diff --git a/tira/biology/fibernet.h b/tira/biology/fibernet.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tira/biology/fibernet.h diff --git a/tira/biomodels/cellset.h b/tira/biomodels/cellset.h new file mode 100644 index 0000000..00a2a45 --- /dev/null +++ b/tira/biomodels/cellset.h @@ -0,0 +1,204 @@ +/* +Copyright <2017> + +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. +*/ + +#ifndef STIM_CELLSET_H +#define STIM_CELLSET_H + +#include +#include +#include +#include +#include + +namespace stim{ + +class cellset{ +private: + static const char delim = ' '; +protected: + std::vector cells; //vector storing field data for each cell + std::unordered_map fields; //unordered map storing field->index information for each field + size_t ip[3]; //hard code to position indices (for speed) + + void init(){ + + } + + /// Initialize fields for a standard cell set containing three points and a radius + void init_p3(){ + fields.insert(std::pair("x", 0)); + ip[0] = 0; + fields.insert(std::pair("y", 1)); + ip[1] = 1; + fields.insert(std::pair("z", 2)); + ip[2] = 2; + } + + void init_p3r(){ + init_p3(); + fields.insert(std::pair("radius", 3)); + ip[3] = 3; + } +public: + /// Constructor - create an empty cell set + cellset(){ + init(); + } + + /// Constructor - load a cellset from a file + cellset(std::string filename){ + init(); //initialize an empty cellset + load(filename); //load the cellset from an existing file + } + + /// Loads a cellset from a file + void load(std::string filename){ + std::ifstream infile(filename); + std::string header; //allocate space for the file header + std::getline(infile, header); //get the file header + + // break the header into fields + std::stringstream ss(header); //create a string stream + std::string field; //store a single field name + size_t i = 0; //current field index + while (std::getline(ss, field, delim)) { //split the header into individual fields + std::pair p(field, i); //create a pair associating the header name with the index + fields.insert(p); //insert the pair into the fields map + i++; //increment the data index + } + size_t nfields = fields.size(); //store the number of fields for each cell + + //load each cell and all associated fields + std::string cell_line; //string holds all information for a cell + std::list cell_list; //list will be temporary storage for the cell fields + while(std::getline(infile, cell_line)){ //for each cell entry + cell_list.push_back(cell_line); //push the cell entry into the list + } + + //convert the list into actual data + size_t ncells = cell_list.size(); //count the number of cells + cells.resize(ncells); //allocate enough space in the array to store all cells + for(size_t c = 0; c < ncells; c++){ //for each cell entry in the list + cells[c] = (double*) malloc(sizeof(double) * nfields); //allocate enough space for each field + std::stringstream fss(cell_list.front()); //turn the string representing the cell list into a stringstream + for(size_t f = 0; f < nfields; f++){ //for each field + fss>>cells[c][f]; //load the field + } + cell_list.pop_front(); //pop the read string off of the front of the list + } + infile.close(); //close the input file + + ip[0] = fields["x"]; //hard code the position indices for speed + ip[1] = fields["y"]; // this assumes all cells have positions + ip[2] = fields["z"]; + } + + /// Return the value a specified field for a cell + /// @param c is the cell index + /// @param f is the field + double value(size_t c, std::string f){ + size_t idx = fields[f]; + return cells[c][idx]; + } + + /// returns an ID used to look up a field + bool exists(std::string f){ + std::unordered_map::iterator iter = fields.find(f); + if(iter == fields.end()) return false; + else return true; + } + + /// Return the position of cell [i] + stim::vec3 p(size_t i){ + stim::vec3 pos(cells[i][ip[0]], cells[i][ip[1]], cells[i][ip[2]]); + return pos; + } + + /// Return the number of cells in the set + size_t size(){ + return cells.size(); + } + + /// Return the maximum value of a field in this cell set + double maximum(std::string field){ + size_t idx = fields[field]; //get the field index + size_t ncells = cells.size(); //get the total number of cells + double maxval, val; //stores the current and maximum values + for(size_t c = 0; c < ncells; c++){ //for each cell + val = cells[c][idx]; //get the field value for this cell + if(c == 0) maxval = val; //if this is the first cell, just assign the maximum + else if(val > maxval) maxval = val; // otherwise text for the size of val and assign it as appropriate + } + return maxval; + } + + /// Return the maximum value of a field in this cell set + double minimum(std::string field){ + size_t idx = fields[field]; //get the field index + size_t ncells = cells.size(); //get the total number of cells + double minval, val; //stores the current and maximum values + for(size_t c = 0; c < ncells; c++){ //for each cell + val = cells[c][idx]; //get the field value for this cell + if(c == 0) minval = val; //if this is the first cell, just assign the maximum + else if(val < minval) minval = val; // otherwise text for the size of val and assign it as appropriate + } + return minval; + } + + /// adds a cell to the cell set + void add(double x, double y, double z, double r = 0){ + + if(cells.size() == 0){ //if the cell set is empty + if(r == 0) //if the radius is zero + init_p3(); //initialize without a radius + else + init_p3r(); //otherwise initialize with a radius + } + + size_t nf = fields.size(); //get the number of fields + size_t bytes = sizeof(double) * nf; //get the size of a cell field + double* newcell = (double*) malloc(bytes); //allocate memory for the new cell + memset(newcell, 0, bytes); //initialize all fields to zero + newcell[ip[0]] = x; + newcell[ip[1]] = y; + newcell[ip[2]] = z; + + if(r != 0){ //if the user specifies a radius + size_t ir = fields["radius"]; //get the index for the radius field + newcell[ir] = r; //add the radius to the field + } + cells.push_back(newcell); //push the new memory entry into the cell array + } + +void save(std::string filename){ + + + size_t ncells = cells.size(); // get the number of cells + std::ofstream file(filename); //open a file to store the cell's coordinates + if (file.is_open()) { + + file << "x y z radius\n"; //write the file header + for (size_t c=0; c < ncells; c++){ //for each cell + if (cells[c][ip[3]] != NULL) //check if for the current cell, radius has been assigned + file << cells[c][ip[0]] << delim << cells[c][ip[1]] << delim << cells[c][ip[2]] << delim << cells[c][ip[3]] << '\n' ; + else //check if for the current cell, radius has not been assigned, set radius to zero + file << cells[c][ip[0]] << delim << cells[c][ip[1]] << delim << cells[c][ip[2]] << delim << 0 << '\n' ; + } + + } + file.close(); + + } + + +}; //end class cellset +}; //end namespace stim + +#endif diff --git a/tira/biomodels/centerline.h b/tira/biomodels/centerline.h new file mode 100644 index 0000000..728bc7c --- /dev/null +++ b/tira/biomodels/centerline.h @@ -0,0 +1,400 @@ +#ifndef JACK_CENTERLINE_H +#define JACK_CENTERLINE_H + +#include +#include +#include +#include + +namespace stim { + + /// we always assume that one centerline has a flow direction even it actually does not have. Also, we allow loop centerline + /// NOTE: centerline is not derived from std::vector> class!!! + template + class centerline { + + private: + size_t n; // number of points on the centerline, can be zero if NULL + + // update length information at each point (distance from starting point) starting from index "start" + void update_L(size_t start = 0) { + L.resize(n); + + if (start == 0) { + L[0] = 0.0f; + start++; + } + + stim::vec3 dir; // temp direction vector for calculating length between two points + for (size_t i = start; i < n; i++) { + dir = C[i] - C[i - 1]; // calculate the certerline extending direction + L[i] = L[i - 1] + dir.len(); // addition + } + } + + protected: + std::vector > C; // points on the centerline + std::vector L; // stores the integrated length along the fiber + + public: + /// constructors + // empty constructor + centerline() { + n = 0; + } + + // constructor that allocate memory + centerline(size_t s) { + n = s; + C.resize(s); // allocate memory for points + L.resize(s); // allocate memory for lengths + + update_L(); + } + + // constructor that constructs a centerline based on a list of points + centerline(std::vector > rhs) { + n = rhs.size(); // get the number of points + C.resize(n); + for (size_t i = 0; i < n; i++) + C[i] = rhs[i]; // copy data + update_L(); + } + + + /// vector operations + // add a new point to current centerline + void push_back(stim::vec3 p) { + C.push_back(p); + n++; // increase the number of points + update_L(n - 1); + } + + // insert a new point at specific location to current centerline + void insert(typename std::vector >::iterator pos, stim::vec3 p) { + C.insert(pos, p); // insert a new point + n++; + size_t d = std::distance(C.begin(), pos); // get the index + update_L(d); + } + // insert a new point at C[idx] + void insert(size_t idx, stim::vec3 p) { + n++; + C.resize(n); + for (size_t i = n - 1; i > idx; i--) // move point location + C[i] = C[i - 1]; + C[idx] = p; + update_L(idx); + } + + // assign a point at specific location to current centerline + void assign(size_t idx, stim::vec3 p) { + C[idx] = p; + update_L(idx); + } + + // erase a point at specific location on current centerline + void erase(typename std::vector >::iterator pos) { + C.erase(pos); // erase a point + n--; + size_t d = std::distance(C.begin(), pos); // get the index + update_L(d); + } + // erase a point at C[idx] + void erase(size_t idx) { + n--; + for (size_t i = idx; i < n; i++) + C[i] = C[i + 1]; + C.resize(n); + update_L(idx); + } + + // clear up all the points + void clear() { + C.clear(); // clear list + L.clear(); // clear length information + n = 0; // set number to zero + } + + // reverse current centerline in terms of points order + centerline reverse() { + centerline result = *this; + + std::reverse(result.C.begin(), result.C.end()); + result.update_L(); + + return result; + } + + /// functions for reading centerline information + // return the number of points on current centerline + size_t size() { + return n; + } + + // return the length + T length() { + return L.back(); + } + + // finds the index of the point closest to the length "l" on the lower bound + size_t findIdx(T l) { + for (size_t i = 1; i < L.size(); i++) { + if (L[i] > l) + return i - 1; + } + + return L.size() - 1; + } + + // get a position vector at the given length into the fiber (based on the pvalue), interpolate + stim::vec3 p(T l, size_t idx) { + T rate = (l - L[idx]) / (L[idx + 1] - L[idx]); + stim::vec3 v1 = C[idx]; + stim::vec3 v2 = C[idx + 1]; + + return (v1 + (v2 - v1) * rate); + } + // get a position vector at the given pvalue(pvalue[0.0f, 1.0f]) + stim::vec3 p(T pvalue) { + // degenerated cases + if (pvalue <= 0.0f) return C[0]; + if (pvalue >= 1.0f) return C.back(); + + T l = pvalue * L.back(); // get the length based on the given pvalue + size_t idx = findIdx(l); + + return p(l, idx); // interpolation + } + + // get the normalized direction vector at point idx (average of the incoming and outgoing directions) + stim::vec3 d(size_t idx) { + if (n <= 1) return stim::vec3(0.0f, 0.0f, 0.0f); // if there is insufficient information to calculate the direction, return null + if (n == 2) return (C[1] - C[0]).norm(); // if there are only two points, the direction vector at both is the direction of the line segment + + // degenerate cases at two ends + if (idx == 0) return (C[1] - C[0]).norm(); // the first direction vector is oriented towards the first line segment + if (idx == n - 1) return (C[n - 1] - C[n - 2]).norm(); // the last direction vector is oriented towards the last line segment + + // all other direction vectors are the average direction of the two joined line segments + stim::vec3 a = C[idx] - C[idx - 1]; + stim::vec3 b = C[idx + 1] - C[idx]; + stim::vec3 ab = a.norm() + b.norm(); + return ab.norm(); + } + + + /// arithmetic operations + // '=' operation + centerline & operator=(centerline rhs) { + n = rhs.n; + C = rhs.C; + L = rhs.L; + + return *this; + } + + // "[]" operation + stim::vec3 & operator[](size_t idx) { + return C[idx]; + } + + // "==" operation + bool operator==(centerline rhs) const { + + if (n != rhs.size()) + return false; + else { + size_t num = rhs.size(); + stim::vec3 tmp; // weird situation that I can only use tmp instead of C itself in comparison + for (size_t i = 0; i < num; i++) { + stim::vec3 tmp = C[i]; + if (tmp[0] != rhs[i][0] || tmp[1] != rhs[i][1] || tmp[2] != rhs[i][2]) + return false; + } + return true; + } + } + + // "+" operation + centerline operator+(stim::vec3 p) const { + centerline result(*this); + result.C.push_back(p); + result.n++; + result.update_L(n - 1); + + return result; + } + centerline operator+(centerline c) const { + centerline result(*this); + size_t num1 = result.size(); + size_t num2 = c.size(); + for (size_t i = 0; i < num2; i++) + result.push_back(c[i]); + result.update_L(num1); + + return result; + } + + + /// advanced operation + // stitch two centerlines if possible (mutual-stitch and self-stitch) + static std::vector > stitch(centerline c1, centerline c2, size_t end = 0) { + std::vector > result; + centerline new_centerline; + stim::vec3 new_vertex; + // ********** for Pavel ********** + // ********** JACK thinks that ultimately we want it AUTOMATEDLY! ********** + + // check stitch case + if (c1 == c2) { // self-stitch case + // ***** don't know how it works ***** + } + else { // mutual-stitch case + size_t num1 = c1.size(); // get the numbers of two centerlines + size_t num2 = c2.size(); + + T* reference = (T*)malloc(sizeof(T) * num1 * 3); // c1 as reference set + T* query = (T*)malloc(sizeof(T) * num2 * 3); // c2 as query set + for (size_t p = 0; p < num1; p++) // read points + for (size_t d = 0; d < 3; d++) + reference[p * 3 + d] = c1[p][d]; // KDTREE is stilla close code, it has its own structure + for (size_t p = 0; p < num2; p++) // read points + for (size_t d = 0; d < 3; d++) + query[p * 3 + d] = c2[p][d]; + + stim::kdtree kdt; + kdt.create(reference, num1, 5); // create a tree based on reference set, max_tree_level is set to 5 + + std::vector index(num2); // stores index results + std::vector dist(num2); + + kdt.search(query, num2, &index[0], &dist[0]); // find the nearest neighbor in c1 for c2 + + // clear up + free(reference); + free(query); + + std::vector::iterator sm = std::min_element(dist.begin(), dist.end()); // find smallest distance + size_t id = std::distance(dist.begin(), sm); // find the target index + + size_t id1 = index[id]; + size_t id2 = id; + + // for centerline c1 + bool flag = false; // flag indicates whether it has already added the bifurcation point to corresponding new centerline + if (id1 == 0 || id1 == num1 - 1) { // splitting bifurcation is on the end + new_centerline = c1; + new_vertex = c2[id2]; + if (id1 == 0) { + new_centerline.insert(0, new_vertex); + flag = true; + } + if (id1 == num1 - 1) { + new_centerline.push_back(new_vertex); + flag = true; + } + + result.push_back(new_centerline); + } + else { // splitting bifurcation is on the centerline + std::vector > tmp_centerline = c1.split(id1); + result = tmp_centerline; + } + + // for centerline c2 + if (id2 == 0 || id2 == num2 - 1) { // splitting bidurcation is on the end + new_centerline = c2; + if (flag) + result.push_back(new_centerline); + else { // add the bifurcation point to this centerline + new_vertex = c1[id1]; + if (id2 == 0) { + new_centerline.insert(0, new_vertex); + flag = true; + } + if (id2 == num2 - 1) { + new_centerline.push_back(new_vertex); + flag = true; + } + + result.push_back(new_centerline); + } + } + else { // splitting bifurcation is on the centerline + std::vector > tmp_centerline = c2.split(id2); + result.push_back(tmp_centerline[0]); + result.push_back(tmp_centerline[1]); + } + } + + return result; + } + + // split current centerline at specific position + std::vector > split(size_t idx) { + std::vector > result; + + // won't split + if (idx <= 0 || idx >= n - 1) { + result.resize(1); + result[0] = *this; // return current centerline + } + // do split + else { + size_t n1 = idx + 1; // vertex idx would appear twice + size_t n2 = n - idx; // in total n + 1 points + + centerline tmp; // temp centerline + + result.resize(2); + + for (size_t i = 0; i < n1; i++) // first half + tmp.push_back(C[i]); + tmp.update_L(); + result[0] = tmp; + tmp.clear(); // clear up for next computation + + for (size_t i = 0; i < n2; i++) // second half + tmp.push_back(C[i + idx]); + tmp.update_L(); + result[1] = tmp; + } + + return result; + } + + // resample current centerline + centerline resample(T spacing) { + + stim::vec3 dir; // direction vector + stim::vec3 tmp; // intermiate point to be added + stim::vec3 p1; // starting point + stim::vec3 p2; // ending point + + centerline result; + + for (size_t i = 0; i < n - 1; i++) { + p1 = C[i]; + p2 = C[i + 1]; + + dir = p2 - p1; // compute the direction of current segment + T seg_len = dir.len(); + + if (seg_len > spacing) { // current segment can be sampled + for (T step = 0.0f; step < seg_len; step += spacing) { + tmp = p1 + dir * (step / seg_len); // add new point + result.push_back(tmp); + } + } + else + result.push_back(p1); // push back starting point + } + result.push_back(p2); // push back ending point + + return result; + } + }; +} + +#endif diff --git a/tira/biomodels/centerline_dep.h b/tira/biomodels/centerline_dep.h new file mode 100644 index 0000000..3f43663 --- /dev/null +++ b/tira/biomodels/centerline_dep.h @@ -0,0 +1,278 @@ +/* +Copyright <2017> + +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. +*/ + +#ifndef STIM_CENTERLINE_H +#define STIM_CENTERLINE_H + +#include +#include + +namespace stim{ + +/** This class stores information about a single fiber represented as a set of geometric points + * between two branch or end points. This class is used as a fundamental component of the stim::network + * class to describe an interconnected (often biological) network. + */ +template +class centerline{ + +protected: + unsigned int N; //number of points in the fiber + double **c; //centerline (array of double pointers) + + /// Initialize an empty fiber + void init(){ + N=0; + c=NULL; + } + + /// Initialize a fiber with N centerline points (all located at [0, 0, 0] with radius 0) + void init(unsigned int n){ + + N = n; //set the number of points + c = (double**) malloc(sizeof(double*) * N); //allocate the array pointer + + for(unsigned int i = 0; i < N; i++) //allocate space for each point + c[i] = (double*) malloc(sizeof(double) * 3); + } + + /// Copies an existing fiber to the current fiber + + /// @param cpy stores the new copy of the fiber + void copy( const stim::centerline& cpy, bool kd = 0){ + + ///allocate space for the new fiber + init(cpy.N); + + ///copy the points + for(unsigned int i = 0; i < N; i++){ + for(unsigned int d = 0; d < 3; d++) //for each dimension + c[i][d] = cpy.c[i][d]; //copy the coordinate + } + } + + /// find distance between two points + double dist(double* p0, double* p1){ + + double sum = 0; // initialize variables + float v; + for(unsigned int d = 0; d < 3; d++) + { + v = p1[d] - p0[d]; + sum +=v * v; + } + return sqrt(sum); + } + + /// Returns a stim::vec representing the point at index i + + /// @param i is an index of the desired centerline point + stim::vec get_vec(unsigned i){ + stim::vec3 r; + r.resize(3); + r[0] = c[i][0]; + r[1] = c[i][1]; + r[2] = c[i][2]; + + return r; + } + + +public: + + centerline(){ + init(); + } + + /// Copy constructor + centerline(const stim::centerline &obj){ + copy(obj); + } + + //initialize a centerline with n points + centerline(int n){ + init(n); + } + + /// Constructor takes a list of stim::vec points, the radius at each point is set to zero + centerline(std::vector< stim::vec > p, bool kd = 0){ + init(p.size()); //initialize the fiber + + //for each point, set the centerline position and radius + for(unsigned int i = 0; i < N; i++){ + + //set the centerline position + for(unsigned int d = 0; d < 3; d++) + c[i][d] = (double) p[i][d]; + } + } + + /// constructor takes a list of points + centerline(std::vector< stim::vec3< T > > pos, bool kd = 0){ + init(pos.size()); //initialize the fiber + + //for each point, set the centerline position and radius + for(unsigned int i = 0; i < N; i++){ + //set the centerline position + for(unsigned int d = 0; d < 3; d++) + c[i][d] = (double) pos[i][d]; + } + } + + /// Assignment operation + centerline& operator=(const centerline &rhs){ + if(this == &rhs) return *this; //test for and handle self-assignment + copy(rhs); + return *this; + } + + + /// Returns the fiber centerline as an array of stim::vec points + std::vector< stim::vec > get_centerline(){ + + //create an array of stim vectors + std::vector< stim::vec3 > pts(N); + + //cast each point to a stim::vec, keeping only the position information + for(unsigned int i = 0; i < N; i++) + pts[i] = stim::vec3((T) c[i][0], (T) c[i][1], (T) c[i][2]); + + //return the centerline array + return pts; + } + + /// Split the fiber at the specified index. If the index is an end point, only one fiber is returned + std::vector< stim::centerline > split(unsigned int idx){ + + std::vector< stim::centerline > fl; //create an array to store up to two fibers + + //if the index is an end point, only the existing fiber is returned + if(idx == 0 || idx == N-1){ + fl.resize(1); //set the size of the fiber to 1 + fl[0] = *this; //copy the current fiber + } + + //if the index is not an end point + else{ + + unsigned int N1 = idx + 1; //calculate the size of both fibers + unsigned int N2 = N - idx; + + fl.resize(2); //set the array size to 2 + + fl[0].init(N1); //set the size of each fiber + fl[1].init(N2); + + //copy both halves of the fiber + unsigned int i, d; + + //first half + for(i = 0; i < N1; i++){ //for each centerline point + for(d = 0; d < 3; d++) + fl[0].c[i][d] = c[i][d]; //copy each coordinate + } + + //second half + for(i = 0; i < N2; i++){ + for(d = 0; d < 3; d++) + fl[1].c[i][d] = c[idx + i][d]; + + } + } + + return fl; //return the array + + } + + /// Outputs the fiber as a string + std::string str(){ + std::stringstream ss; + + //create an iterator for the point list + //typename std::list< point >::iterator i; + for(unsigned int i = 0; i < N; i++){ + ss<<" [ "; + for(unsigned int d = 0; d < 3; d++){ + ss< operator[](unsigned i){ + return get_vec(i); + } + + /// Back method returns the last point in the fiber + stim::vec back(){ + return get_vec(N-1); + } + ////resample a fiber in the network + stim::centerline resample(T spacing) + { + std::cout<<"fiber::resample()"< v(3); //v-direction vector of the segment + stim::vec p(3); //- intermediate point to be added + stim::vec p1(3); // p1 - starting point of an segment on the fiber, + stim::vec p2(3); // p2 - ending point, + double sum=0; //distance summation + std::vector > fiberPositions = centerline(); + std::vector > newPointList; // initialize list of new resampled points on the fiber + // for each point on the centerline (skip if it is the last point on centerline) + for(unsigned int f=0; f< N-1; f++) + { + + p1 = fiberPositions[f]; p2 = fiberPositions[f + 1]; v = p2 - p1; + for(unsigned int d = 0; d < 3; d++){ + sum +=v[d] * v[d];} //length of segment-distance between starting and ending point + + T lengthSegment = sqrt(sum); //find Length of the segment as distance between the starting and ending points of the segment + + if(lengthSegment >= spacing) // if length of the segment is greater than standard deviation resample + { + // repeat resampling until accumulated stepsize is equsl to length of the segment + for(T step=0.0; step + +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. +*/ +#ifndef STIM_FLOW_H +#define STIM_FLOW_H + +#include +#include + +//STIM include +#include +#include +#include + +#ifdef __CUDACC__ +#include +#include +#endif + +namespace stim { + template + struct triple { + A first; + B second; + C third; + }; + + template + struct bridge { + std::vector v; // vertices' indices + std::vector > V; // vertices' coordinates + T l; // length + T r; // radii + T deltaP; // pressure drop + T Q; // volume flow rate + }; + + template + class flow { + + private: + + // calculate the cofactor of elemen[row][col] + void get_minor(T** src, T** dest, int row, int col, int order) { + + // index of element to be copied + int rowCount = 0; + int colCount = 0; + + for (int i = 0; i < order; i++) { + if (i != row) { + colCount = 0; + for (int j = 0; j < order; j++) { + // when j is not the element + if (j != col) { + dest[rowCount][colCount] = src[i][j]; + colCount++; + } + } + rowCount++; + } + } + } + + // calculate the det() + T determinant(T** mat, int order) { + + // degenate case when n = 1 + if (order == 1) + return mat[0][0]; + + T det = 0.0; // determinant value + + // allocate the cofactor matrix + T** minor = (T**)malloc((order - 1) * sizeof(T*)); + for (int i = 0; i < order - 1; i++) + minor[i] = (T*)malloc((order - 1) * sizeof(T)); + + + for (int i = 0; i < order; i++) { + + // get minor of element(0, i) + get_minor(mat, minor, 0, i, order); + + // recursion + det += (i % 2 == 1 ? -1.0 : 1.0) * mat[0][i] * determinant(minor, order - 1); + } + + // release memory + for (int i = 0; i < order - 1; i++) + free(minor[i]); + free(minor); + + return det; + } + + public: + T** C; // Conductance + std::vector > Q; // volume flow rate + std::vector QQ; // Q' vector + std::vector P; // initial pressure + std::vector pressure; // final pressure + + //std::vector > V; // velocity + //std::vector > Q; // volume flow rate + //std::vector > deltaP; // pressure drop + + flow() {} // default constructor + + void init(unsigned n_e, unsigned n_v) { + + C = new T*[n_v](); + for (unsigned i = 0; i < n_v; i++) { + C[i] = new T[n_v](); + } + + QQ.resize(n_v); + P.resize(n_v); + pressure.resize(n_v); + + Q.resize(n_e); + } + + void reset(unsigned n_v) { + + for (unsigned i = 0; i < n_v; i++) { + for (unsigned j = 0; j < n_v; j++) { + C[i][j] = 0; + } + } + } + + void clear(unsigned n_v) { + + for (unsigned i = 0; i < n_v; i++) + delete[] C[i]; + delete[] C; + } + + /// Calculate the inverse of A and store the result in C + void inversion(T** A, int order, T* C) { + +#ifdef __CUDACC__ + + // convert from double pointer to single pointer, make it flat + T* Aflat = (T*)malloc(order * order * sizeof(T)); + for (unsigned i = 0; i < order; i++) + for (unsigned j = 0; j < order; j++) + Aflat[i * order + j] = A[i][j]; + + // create device pointer + T* d_Aflat; // flat original matrix + T* d_Cflat; // flat inverse matrix + T** d_A; // put the flat original matrix into another array of pointer + T** d_C; + int *d_P; + int *d_INFO; + + // allocate memory on device + HANDLE_ERROR(cudaMalloc((void**)&d_Aflat, order * order * sizeof(T))); + HANDLE_ERROR(cudaMalloc((void**)&d_Cflat, order * order * sizeof(T))); + HANDLE_ERROR(cudaMalloc((void**)&d_A, sizeof(T*))); + HANDLE_ERROR(cudaMalloc((void**)&d_C, sizeof(T*))); + HANDLE_ERROR(cudaMalloc((void**)&d_P, order * 1 * sizeof(int))); + HANDLE_ERROR(cudaMalloc((void**)&d_INFO, 1 * sizeof(int))); + + // copy matrix from host to device + HANDLE_ERROR(cudaMemcpy(d_Aflat, Aflat, order * order * sizeof(T), cudaMemcpyHostToDevice)); + + // copy matrix from device to device + HANDLE_ERROR(cudaMemcpy(d_A, &d_Aflat, sizeof(T*), cudaMemcpyHostToDevice)); + HANDLE_ERROR(cudaMemcpy(d_C, &d_Cflat, sizeof(T*), cudaMemcpyHostToDevice)); + + // calculate the inverse of matrix based on cuBLAS + cublasHandle_t handle; + CUBLAS_HANDLE_ERROR(cublasCreate_v2(&handle)); // create cuBLAS handle object + + CUBLAS_HANDLE_ERROR(cublasSgetrfBatched(handle, order, d_A, order, d_P, d_INFO, 1)); + + int INFO = 0; + HANDLE_ERROR(cudaMemcpy(&INFO, d_INFO, sizeof(int), cudaMemcpyDeviceToHost)); + if (INFO == order) + { + std::cout << "Factorization Failed : Matrix is singular." << std::endl; + cudaDeviceReset(); + exit(1); + } + + CUBLAS_HANDLE_ERROR(cublasSgetriBatched(handle, order, (const T **)d_A, order, d_P, d_C, order, d_INFO, 1)); + + CUBLAS_HANDLE_ERROR(cublasDestroy_v2(handle)); + + // copy inverse matrix from device to device + HANDLE_ERROR(cudaMemcpy(&d_Cflat, d_C, sizeof(T*), cudaMemcpyDeviceToHost)); + + // copy inverse matrix from device to host + HANDLE_ERROR(cudaMemcpy(C, d_Cflat, order * order * sizeof(T), cudaMemcpyDeviceToHost)); + + // clear up + free(Aflat); + HANDLE_ERROR(cudaFree(d_Aflat)); + HANDLE_ERROR(cudaFree(d_Cflat)); + HANDLE_ERROR(cudaFree(d_A)); + HANDLE_ERROR(cudaFree(d_C)); + HANDLE_ERROR(cudaFree(d_P)); + HANDLE_ERROR(cudaFree(d_INFO)); + +#else + // get the determinant of a + double det = 1.0 / determinant(A, order); + + // memory allocation + T* tmp = (T*)malloc((order - 1)*(order - 1) * sizeof(T)); + T** minor = (T**)malloc((order - 1) * sizeof(T*)); + for (int i = 0; i < order - 1; i++) + minor[i] = tmp + (i * (order - 1)); + + for (int j = 0; j < order; j++) { + for (int i = 0; i < order; i++) { + // get the co-factor (matrix) of A(j,i) + get_minor(A, minor, j, i, order); + C[i][j] = det * determinant(minor, order - 1); + if ((i + j) % 2 == 1) + C[i][j] = -C[i][j]; + } + } + + // release memory + free(tmp); + free(minor); +#endif + } + }; +} + +#endif + + + +//// calculate the flow rate of 3D model(circle cross section) +//void calculate_flow_rate(unsigned e, T r) { +// stim::triple tmp_Q; +// tmp_Q.first = V[e].first; // copy the vertices information +// tmp_Q.second = V[e].second; +// tmp_Q.third = V[e].third * stim::PI * pow(r, 2); // UNITS: uL/s +// Q.push_back(tmp_Q); // push back the volume flow rate information for every edge +//} + +//// calculate the flow rate of 2D model(rectangular cross section) +//void calculate_flow_rate(unsigned e, T r, T h) { +// stim::triple tmp_Q; +// tmp_Q.first = V[e].first; // copy the vertices information +// tmp_Q.second = V[e].second; +// tmp_Q.third = V[e].third * h * r; // UNITS: uL/s = mm^3/s +// Q.push_back(tmp_Q); // push back the volume flow rate information for every edge +//} + +//// calculate the pressure drop of 3D model(circle cross section) +//void calculate_deltaP(unsigned e, T u, T l, T r) { +// stim::triple tmp_deltaP; +// tmp_deltaP.first = V[e].first; // copy the vertices information +// tmp_deltaP.second = V[e].second; +// tmp_deltaP.third = (8 * u * l * Q[e].third) / (stim::PI * pow(r, 4)); // UNITS: g/mm/s^2 = Pa +// deltaP.push_back(tmp_deltaP); // push back the volume flow rate information for every edge +//} + +//// calculate the pressure drop of 2D model(rectangular cross section) +//void calculate_deltaP(unsigned e, T u, T l, T r, T h) { +// stim::triple tmp_deltaP; +// tmp_deltaP.first = V[e].first; // copy the vertices information +// tmp_deltaP.second = V[e].second; +// tmp_deltaP.third = (12 * u * l * Q[e].third) / (h * pow(r, 3)); // UNITS: g/mm/s^2 = Pa +// deltaP.push_back(tmp_deltaP); // push back the volume flow rate information for every edge +//} + +//// better way to do this??? +//// find the maximum and minimum pressure positions +//void find_max_min_pressure(size_t n_e, size_t n_v, unsigned& max, unsigned& min) { +// std::vector P(n_v, FLT_MAX); +// // set one to reference +// P[Q[0].first] = 0.0; +// unsigned first = 0; +// unsigned second = 0; +// // calculate all the relative pressure in brute force manner +// for (unsigned e = 0; e < n_e; e++) { +// // assuming the obj file stores in a straight order, in other words, like swc file +// first = Q[e].first; +// second = Q[e].second; +// if (P[first] != FLT_MAX) // if pressure at start vertex is known +// P[second] = P[first] - deltaP[e].third; +// else if (P[second] != FLT_MAX) // if pressure at end vertex is known +// P[first] = P[second] + deltaP[e].third; +// } + +// // find the maximum and minimum pressure position +// auto m1 = std::max_element(P.begin(), P.end()); // temporarily max number +// auto m2 = std::min_element(P.begin(), P.end()); // temporarily min number + +// max = std::distance(P.begin(), m1); +// min = std::distance(P.begin(), m2); + +// T tmp_m = *m2; +// // Now set the lowest pressure port to reference pressure(0.0 Pa) +// for (unsigned i = 0; i < n_v; i++) +// P[i] -= tmp_m; + +// for (unsigned i = 0; i < n_v; i++) +// pressure.push_back(P[i]); +//} \ No newline at end of file diff --git a/tira/biomodels/flow_dep.h b/tira/biomodels/flow_dep.h new file mode 100644 index 0000000..22f4f8b --- /dev/null +++ b/tira/biomodels/flow_dep.h @@ -0,0 +1,415 @@ +/* +Copyright <2017> + +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. +*/ + +#pragma once +#include // Required for ofstream, etc. +#include // Required for setw +#include // Required for cout, cin, etc. +#include // Required for returning multiple values from a function + +using namespace std; + + +class flow +{ +public: + void backupToTxt(unsigned int nL, double **D, char filename[]); + tuple copySrcDesRadLen(char filename[]); + void copyToArray(int *src, int *dest, double *radii, double *len); + int getDangleNodes(int datarow, int numNodes, int *row, int *column, int *dangleNodes); + void inversion(double **a, int n, double **b); + +protected: + float determinant(double **a, int n); + int minor(double **src, double **dest, int row, int col, int order); +}; + +/* Function to find the dangle nodes in a network */ +// Created by Cherub P. Harder (8/10/2015), U of Houston +// Modified by Cherub P. Harder on 8/12/2015 +int flow::getDangleNodes(int datarow, int numNodes, int *column1, int *column2, int *dangleNodes) +{ + int count = datarow, diff1 = 0, diff2 = 0, numPress = 0, st = 0; + + // Find matching nodes within column2 + for( int i = 0; i < count; i++ ) + { + for( int y = i+1; y < datarow; y++ ) + { + if( column2[i] == column2[y] ) // Is there a match? + { + st = column2[i]; // Save the matching node +// cout << endl << column2[i] << " = " << column2[y] << endl; // Display the matching nodes + memmove(column2+i, column2+i+1, (datarow-(i+1)) * sizeof(column2[0])); // Move up the rows + // taking the places of the rows before them starting + // with where the matching node is located + column2[datarow-1] = st; // Place the matching node at the very end of the array-- + // this is for comparison purpose so that the other match- + // ing node will be moved as well and then omitted later. + diff1++; // Count the matching node + + // Display the updated array (with the matching node moved to the bottommost row) +/* cout << "Updated array:" << endl; + for( int k = 0; k < datarow; k++ ) + cout << column2[k] << endl; +*/ + // Decrement the counters + // NOTE: The counters need to be decremented because the rows had been moved up, so the same + // locations need to be read again because they contain different values now after the move. + i--; // Decrement i to read the node that took over the place + // of the matching node. Otherwise, it will be skipped. + y--; // Decrement y to read the following node for comparison + count--; // The maximum count need to be decremented so that the + // matching nodes that had been moved will not be read again. + // However, the maximum count (datarow) for finding a match + // will not be decremented because the remaining matching + // node that has not been moved yet needs to be moved and + // the only way to do that is to match it with its duplicate. + } + } + } + + // Store the nodes that have no duplicates + // NOTE: This will only save the nodes that have not been moved to the bottom. +// cout << "\ndangleNodes array:" << endl; + for( int j = 0; j < datarow-diff1; j++ ) + { + dangleNodes[numPress] = column2[j]; +// cout << dangleNodes[j] << endl; // DELETE!!! + numPress++; // Count the non-duplicated node + } + + // Find if the non-duplicated nodes have a match from column1 + count = datarow-diff1; // Reinitialize the counter + + for( int i = 0; i < count; i++ ) + { + for( int j = 0; j < datarow; j++ ) + { + if( dangleNodes[i] == column1[j] ) // Is there a match? + { + st = column1[j]; // Save the matching node +// cout << endl << dangleNodes[i] << " = " << column1[j] << endl; // Display the matching nodes + memmove(dangleNodes+i, dangleNodes+i+1, (datarow-diff1-(i+1)) * sizeof(dangleNodes[0])); + dangleNodes[count-1] = st; // Move the matching node to the bottom of the array + diff2++; // Count the matching node + + // Display the updated array +/* cout << "Updated dangleNodes array:" << endl; + for( int k = 0; k < count-1; k++ ) + { + cout << dangleNodes[k] << endl; + } +*/ + // Decrement the counters + i--; + j--; + count--; + numPress--; // Decrement to the exact number of dangle nodes + } + } + } + + return numPress; // Number of dangle nodes +} + + +// Function to make a backup copy of the contents of a matrix to a .txt file +// Created by Cherub P. Harder (8/10/2015), U of Houston +void flow::backupToTxt(unsigned int nL, double **D, char filename[]) +{ + ofstream output_file(filename); + + for( unsigned int i = 0; i < nL; i++ ) + { + for( int j = 0; j < 4; j++ ) + { + if( j < 3 ) + output_file << D[i][j] << "\t"; + + else + output_file << D[i][j]; + } + + output_file << "\n"; + } + + output_file.close( ); +} + + +// Function to make separate copies of the source nodes, destination nodes, radii, and lengths +// Created by Cherub P. Harder (8/10/2015), U of Houston +tuple flow::copySrcDesRadLen(char filename[]) +{ + int cnt = 0, numElements = 0, numNodes = 0; + float number = 0.0; + ofstream srcData("srcCol.txt"); // A .txt file to store the source nodes + ofstream desData("destCol.txt"); // A .txt file to store the destination nodes + ofstream radiiData("radii.txt"); // A .txt file to store the radii + ofstream lenData("lengths.txt"); // A .txt file to store the lengths + FILE *fp = fopen(filename, "r"); // Create a variable of type FILE* and open the file using + // the fopen function and assign the file to the variable + // Check if the file exists + if(fp == NULL) // Alternative: if(!fp) + { + printf("Error! File does not exist.\n"); + getchar( ); // Pause + exit(-1); // NOTE: Must include stdlib.h. + } + + // Store data to their respective .txt files + while(fscanf(fp, "%f", &number) == 1) + { + cnt++; // Increment counter + + // Store to srcCol.txt + if(cnt == 1) + srcData << number << endl; + + // Store to destCol.txt + if(cnt == 2) + desData << number << endl; + + // Save the current number of nodes + if(cnt < 3) + { + if(number > numNodes) + numNodes = (int)number; + } + + // Store to radii.txt + if(cnt == 3) + radiiData << number << endl; + + // Store to lengths.txt + if(cnt == 4) + { + lenData << number << endl; + + numElements++; // Count the elements + cnt = 0; // Reset counter + } + } + + srcData.close( ); + desData.close( ); + radiiData.close( ); + lenData.close( ); + + return make_tuple(numNodes, numElements); // Return two values +} + + +// Function to copy data for .txt files to their respective arrays +// Created by Cherub P. Harder (8/11/2015), U of Houston +void flow::copyToArray(int *src, int *dest, double *radii, double *len) +{ + int v = 0; + double tmp = 0, R = 0, L = 0; + + // Store source node values to the array src + ifstream readSrc("srcCol.txt"); + + while( readSrc >> tmp ) + { + src[v] = (int)tmp; + v++; + } + + readSrc.close( ); + + // Store destination node values to the array dest + v = 0; // Reset counter + ifstream readDest("destCol.txt"); + + while( readDest >> tmp ) + { + dest[v] = (int)tmp; + v++; + } + + readDest.close( ); + + // Store radius values to the array radii + v = 0; // Reset counter + ifstream readRad("radii.txt"); + + while( readRad >> tmp ) + { + radii[v] = tmp; + v++; + } + + readRad.close( ); + + // Store length values to the array len + v = 0; // Reset counter + ifstream readLen("lengths.txt"); + + while( readLen >> tmp ) + { + len[v] = tmp; + v++; + } + + readLen.close( ); +} + + +// Function to find the inverse of a square matrix +void flow::inversion(double **a, int n, double **b) +{ + // Get 1 over the determinant of A + double det = (double)(1.0/determinant(a, n)); +// cerr << "\n1/det(C) = " << det << endl; // DELETE!!! + + // Memory allocation + double *tmp = new double[(n-1) * (n-1)]; + double **m = new double * [n-1]; + for( int i = 0; i < n-1; i++ ) + m[i] = tmp + ( i * (n-1) ); + + for( int j = 0; j < n; j++) + { + for( int i = 0; i < n; i++ ) + { + // Get the cofactor (matrix) of a(j,i) + minor(a, m, j, i, n); + b[i][j] = det * determinant( m, n-1 ); + if( (i+j)%2 == 1 ) + b[i][j] = -b[i][j]; + } + } + + // Release memory + // Delete [] minor[0]; + delete [] tmp; + delete [] m; +} + + +// Function to find the determinant of a matrix using recursion +// Contribution by Edward Popko +// Modified by Cherub P. Harder (7/15/2015), U of Houston +// Arguments: a(double **) - pointer to a pointer of an arbitrary square matrix +// n(int) - dimension of the square matrix +float flow::determinant(double **a, int n) +{ + int i, j, j1, j2; // General loop and matrix subscripts + double det = 0; // Initialize determinant + double **m = NULL; // Pointer to pointer to implement 2D square array + + // Display contents of matrix C (DELETE!!!) +/* std::cout << "\nThe updated matrix C:\n"; + for( int j = 0; j < n; ++j ) + { + std::cerr << "\t"; + + for( int k = 0; k < n; ++k ) + std::cerr << left << setw(15) << a[j][k]; + + std::cerr << endl; + } + + getchar(); // DELETE!!!*/ + + if(n < 1) { } // Error condition - should never get here + + else if (n == 1) // Should never get here + { + det = a[0][0]; + } + + else if(n == 2) // Basic 2x2 sub-matrix determinate definition + { // When n == 2, this ends the recursion series + det = a[0][0] * a[1][1] - a[1][0] * a[0][1]; + } + // Recursion continues, solve next sub-matrix + else // Solve the next minor by building a sub-matrix + { + det = 0; // Initialize determinant of sub-matrix + + for (j1 = 0; j1 < n; j1++) // For each column in sub-matrix get space for the + { // pointer list + m = (double **) malloc((n-1) * sizeof(double *)); + + for (i = 0; i < n-1; i++) + m[i] = (double *) malloc((n-1)* sizeof(double)); + // i[0][1][2][3] first malloc + // m -> + + + + space for 4 pointers + // | | | | j second malloc + // | | | +-> _ _ _ [0] pointers to + // | | +----> _ _ _ [1] and memory for + // | +-------> _ a _ [2] 4 doubles + // +----------> _ _ _ [3] + // + // a[1][2] + // Build sub-matrix with minor elements excluded + + for (i = 1; i < n; i++) + { + j2 = 0 ; // Start at first sum-matrix column position + // Loop to copy source matrix less one column + for (j = 0; j < n; j++) + { + if (j == j1) continue; // Do not copy the minor column element + + m[i-1][j2] = a[i][j]; // Copy source element into new sub-matrix + // i-1 because new sub-matrix is one row + // (and column) smaller with excluded minors + j2++; // Move to next sub-matrix column position + } + } + + det += (double)pow(-1.0, 1.0 + j1 + 1.0) * a[0][j1] * determinant(m, n-1); + // Sum x raised to y power + // recursively get determinant of next + // sub-matrix which is now one + // row & column smaller + + for (i = 0; i < n-1; i++) free(m[i]); // Free the storage allocated to + // this minor's set of pointers + free(m); // Free the storage for the original + // pointer to pointer + } + } + + return(det); +} + + +// Function to calculate the cofactor of element (row, col) +int flow::minor(double **src, double **dest, int row, int col, int order) +{ + // Indicate which col and row is being copied to dest + int colCount=0,rowCount=0; + + for(int i = 0; i < order; i++) + { + if(i != row) + { + colCount = 0; + for(int j = 0; j < order; j++) + { + // When j is not the element + if( j != col ) + { + dest[rowCount][colCount] = src[i][j]; + colCount++; + } + } + + rowCount++; + } + } + + return 1; +} \ No newline at end of file diff --git a/tira/biomodels/network.h b/tira/biomodels/network.h new file mode 100644 index 0000000..0d0429b --- /dev/null +++ b/tira/biomodels/network.h @@ -0,0 +1,1461 @@ +#ifndef JACK_NETWORK_H +#define JACK_NETWORK_H + +#include "centerline.h" +#include +#include +#include +#include + + +// *****help function***** + +template +CUDA_CALLABLE T gaussian(T x, T std = 25.0f) { + return exp(-x / (2.0f * std * std)); +} + +#ifdef __CUDACC__ +template +__global__ void find_metric(T* M, size_t n, T* D, T sigma) { + size_t x = blockDim.x * blockIdx.x + threadIdx.x; + if (x >= n) return; // segfault + M[x] = 1.0f - gaussian(D[x], sigma); +} +#endif + +namespace stim{ + + template + class network { + public: + // define edge class that extends centerline class with radius information + class edge : public stim::centerline { + protected: + std::vector R; // radius at each point on current edge + + public: + unsigned int v[2]; // unique idx for starting and ending point + using stim::centerline::d; + using stim::centerline::C; + + + /// constructors + // empty constructor + edge() : stim::centerline() { + v[0] = UINT_MAX; // set to default value, risky! + v[1] = UINT_MAX; + } + + // constructor that contructs an edge based on a centerline + edge(stim::centerline c) : stim::centerline(c) { + size_t num = c.size(); + R.resize(num); + } + + // constructor that constructs an edge based on a centerline and a list of radii + edge(stim::centerline c, std::vector r) : stim::centerline(c) { + R = r; // copy radii + } + + // constructor that constructs an edge based on a list of points and a list of radii + edge(std::vector > c, std::vector r) : stim::centerline(c) { + R = r; // copy radii + } + + + /// basic operations + // get radius + T r(size_t idx) { + return R[idx]; + } + + // set radius + void set_r(T value) { + size_t num = R.size(); + for (size_t i = 0; i < num; i++) + R[i] = value; + } + void set_r(size_t idx, T value) { + R[idx] = value; + } + void set_r(std::vector value) { + size_t num = value.size(); + for (size_t i = 0; i < num; i++) + R[i] = value[i]; + } + + // push back a new radius + void push_back_r(T value) { + R.push_back(value); + } + + + /// vector operation + // insert a new point and radius at specific location + void insert(size_t idx, stim::vec3 p, T r) { + centerline::insert(idx, p); // insert a new point on current centerline + + R.insert(R.begin() + idx, r); // insert a new radius for that point + } + + // reverse the order of an edge + edge reverse() { + centerline new_centerline = (*this).centerline::reverse(); + std::vector new_radius = R; + std::reverse(new_radius.begin(), new_radius.end()); + + edge result(new_centerline, new_radius); + + return result; + } + + // copy edge to array + void edge_to_array(T* a) { + edge b = (*this); + size_t n = b.size(); + for (size_t i = 0; i < n; i++) { + a[i * 3 + 0] = b[i][0]; + a[i * 3 + 1] = b[i][1]; + a[i * 3 + 2] = b[i][2]; + } + } + + + /// arithmetic operations + // '+' operation + edge operator+(edge e) const { + edge result(*this); + size_t num = e.size(); + for (size_t i = 0; i < num; i++) { + result.push_back(e[i]); + result.push_back_r(e.R[i]); + } + + return result; + } + + + /// advanced operations + // concatenate two edges from specific point, The deference between function "+" and "concatenate" is that this requires that new edges should share a joint point + edge concatenate(edge e2, size_t p1, size_t p2) { + edge e1 = *this; + size_t num1 = e1.size(); // get the number of points + size_t num2 = e2.size(); + centerline new_centerline; + std::vector new_radius; + + // four situations + if (p1 == 0) { + if (p2 == 0) + e2 = e2.reverse(); + for (size_t i = 0; i < num2 - 1; i++) { + new_centerline.push_back(e2[i]); + new_radius.push_back(e2.R[i]); + } + for (size_t i = 0; i < num1; i++) { + new_centerline.push_back(e1[i]); + new_radius.push_back(e1.R[i]); + } + } + else { + if (p2 != 0) + e2 = e2.reverse(); + for (size_t i = 0; i < num1 - 1; i++) { + new_centerline.push_back(e1[i]); + new_radius.push_back(e1.R[i]); + } + for (size_t i = 0; i < num2; i++) { + new_centerline.push_back(e2[i]); + new_radius.push_back(e2.R[i]); + } + } + + edge result(new_centerline, new_radius); + + return result; + } + + // split current edge at specific position + std::vector split(size_t idx) { + + // can't update v!!! + std::vector > tmp; + tmp = (*this).centerline::split(idx); // split current edge in terms of centerline + size_t num = tmp.size(); + std::vector result(num); // construct a list of edges + for (size_t i = 0; i < num; i++) { + edge new_edge(tmp[i]); // construct new edge based on one centerline + result[i] = new_edge; + } + + for (size_t i = 0; i < num; i++) { // for every edge + for (size_t j = 0; j < result[i].size(); j++) { // for every point on that edge + result[i].R[j] = R[j + i * idx]; // update radius information + } + } + + return result; + } + + // resample current edge + edge resample(T spacing) { + edge result(centerline::resample(spacing)); // resample current edge and output as a new edge + result.v[0] = v[0]; // updates unique indices + result.v[1] = v[1]; + + return result; + } + + // compute a circle that represent the shape of cylinder cross section at point idx, (TGC -> truncated generalized cones) + stim::circle circ(size_t idx) { + + stim::circle c; // create a circle to orient for finding the circle plane at point idx + c.rotate(d(idx)); // rotate the circle + stim::vec3 U = c.U; // copy the frenet frame vector + + return stim::circle(C[idx], R[idx], d(idx), U); + } + + /// output operation + // output the edge information as a string + std::string str() { + std::stringstream ss; + ss << "(" << centerline::size() << ")\tl = " << this->length() << "\t" << v[0] << "----" << v[1]; + return ss.str(); + } + + /// operator for writing the edge information into a binary .nwt file. + friend std::ofstream& operator<<(std::ofstream& out, edge& e) { + out.write(reinterpret_cast(&e.v[0]), sizeof(unsigned int)); // write the starting point. + out.write(reinterpret_cast(&e.v[1]), sizeof(unsigned int)); // write the ending point. + size_t sz = e.size(); // write the number of point in the edge. + out.write(reinterpret_cast(&sz), sizeof(unsigned int)); + for (size_t i = 0; i < sz; i++) { // write each point + stim::vec3 point = e[i]; + out.write(reinterpret_cast(&point[0]), 3 * sizeof(T)); + out.write(reinterpret_cast(&e.R[i]), sizeof(T)); // write the radius + } + return out; // return stream + } + + /// operator for reading an edge from a binary .nwt file. + friend std::ifstream& operator>>(std::ifstream& in, edge& e) { + unsigned int v0, v1, sz; + in.read(reinterpret_cast(&v0), sizeof(unsigned int)); // read the staring point. + in.read(reinterpret_cast(&v1), sizeof(unsigned int)); // read the ending point + in.read(reinterpret_cast(&sz), sizeof(unsigned int)); // read the number of points in the edge + + std::vector > p(sz); + std::vector r(sz); + for (size_t i = 0; i < sz; i++) { // set the points and radii to the newly read values + stim::vec3 point; + in.read(reinterpret_cast(&point[0]), 3 * sizeof(T)); + p[i] = point; + T mag; + in.read(reinterpret_cast(&mag), sizeof(T)); + r[i] = mag; + } + e = edge(p, r); + e.v[0] = v0; e.v[1] = v1; + return in; + } + }; + + // define vertex class that extends vec3 class with connectivity information + class vertex : public stim::vec3 { + public: + std::vector e[2]; // incoming and outgoing edges of that vertex + using stim::vec3::ptr; + + /// constructors + // empty constructor + vertex() : stim::vec3() { + } + + // constructor that constructs a vertex based on a vec3 vector + vertex(stim::vec3 v) : stim::vec3(v) { + } + + stim::vec3 + getPosition() + { + return stim::vec3(ptr[0], ptr[1], ptr[2]); + } + + /// output operation + // output the vertex information as a string + std::string str() { + std::stringstream ss; + ss << "\t(x, y, z) = " << stim::vec3::str(); + + if (e[0].size() > 0) { + ss << "\t> "; + for (size_t i = 0; i < e[0].size(); i++) + ss << e[0][i] << " "; + } + if (e[1].size() > 0) { + ss << "\t< "; + for (size_t i = 0; i < e[1].size(); i++) + ss << e[1][i] << " "; + } + + return ss.str(); + } + + /// operator for writing the vector into the stream; + friend std::ofstream& operator<<(std::ofstream& out, const vertex& v) { + unsigned int s0, s1; + s0 = v.e[0].size(); + s1 = v.e[1].size(); + out.write(reinterpret_cast(&v.ptr[0]), 3 * sizeof(T)); // write physical vertex location + out.write(reinterpret_cast(&s0), sizeof(unsigned int)); // write the number of "outgoing edges" + out.write(reinterpret_cast(&s1), sizeof(unsigned int)); // write the number of "incoming edges" + if (s0 != 0) + out.write(reinterpret_cast(&v.e[0][0]), sizeof(unsigned int)*v.e[0].size()); // write the "outgoing edges" + if (s1 != 0) + out.write(reinterpret_cast(&v.e[1][0]), sizeof(unsigned int)*v.e[1].size()); // write the "incoming edges" + return out; + } + + /// operator for reading the vector out of the stream; + friend std::ifstream& operator >> (std::ifstream& in, vertex& v) { + in.read(reinterpret_cast(&v[0]), 3 * sizeof(T)); // read the physical position + unsigned int s[2]; + in.read(reinterpret_cast(&s[0]), 2 * sizeof(unsigned int)); // read the sizes of incoming and outgoing edge arrays + + std::vector one(s[0]); + std::vector two(s[1]); + v.e[0] = one; + v.e[1] = two; + if (one.size() != 0) + in.read(reinterpret_cast(&v.e[0][0]), s[0] * sizeof(unsigned int)); // read the arrays of "outgoing edges" + if (two.size() != 0) + in.read(reinterpret_cast(&v.e[1][0]), s[1] * sizeof(unsigned int)); // read the arrays of "incoming edges" + return in; + } + }; + + public: + + std::vector E; // list of edges + std::vector V; // list of vertices + + /// constructors + // empty constructor + network() { + } + + // constructor with a file to load + network(std::string fileLocation) { + load_obj(fileLocation); + } + + // constructor that constructs a network based on lists of vertices and edges + network(std::vector nE, std::vector nV) { + E = nE; + V = nV; + } + + + /// basic operations + // get the number of edges + size_t edges() { + return E.size(); + } + + // get the number of vertices + size_t vertices() { + return V.size(); + } + + // get the radius at specific point + T r(size_t f, size_t p) { // edge f, point p + return E[f].r(p); + } + T r(size_t c) { // vertex c + T result; + if (V[c].e[0].size()) { // if this vertex has outgoing edges + size_t f = V[c].e[0][0]; // get the index of first outgoing edge of this vertex + result = r(f, 0); // this vertex should be the starting point of that edge + } + else { // if this vertex only has incoming edges + size_t f = V[c].e[1][0]; // get the index of first incoming edge of this vertex + result = r(f, E[f].size() - 1); // this vertex should be the ending point of that edge + } + + return result; + } + + // get the average radius of one specific edge + T ar(size_t f) { + T result = 0.0f; + size_t num = E[f].size(); + for (size_t i = 0; i < num; i++) + result += E[f].R[i]; + result = result / num; + + return result; + } + + // get the length of edge "f" + T length(size_t f) { + return E[f].length(); + } + + // copy specific edge + edge get_edge(size_t f) { + return E[f]; + } + + // copy specific vertex + vertex get_vertex(size_t c) { + return V[c]; + } + + // get boundary/pendant vertices + std::vector pendant() { + std::vector result; + + for (size_t i = 0; i < V.size(); i++) + if (V[i].e[0].size() + V[i].e[1].size() == 1) + result.push_back(i); + + return result; + } + + // set radius for specific point on edge "f" + void set_r(size_t f, size_t p, T value) { + E[f].set_r(p, value); + } + void set_r(size_t f, T value) { + E[f].set_r(value); + } + void set_r(size_t f, std::vector value) { + E[f].set_r(value); + } + + // copy all points (coordinates) to 1D array + void copy_to_array(T* dst) { + size_t t = 0; // indicator for points + for (size_t i = 0; i < E.size(); i++) { + for (size_t j = 0; j < E[i].size(); j++) { + for (size_t k = 0; k < 3; k++) { + dst[t * 3 + k] = E[i][j][k]; + } + t++; // next point + } + } + } + + // get an average of branching index in the network + T BranchingIndex() { + T B = 0.0f; + size_t num = V.size(); + for (size_t i = 0; i < num; i++) + B += (T)(V[i].e[0].size() + V[i].e[1].size()); + + B = B / (T)num; + + return B; + } + + // get the number of branching points in the network + size_t BranchP() { + size_t B = 0; + size_t c; + size_t num = V.size(); + for (size_t i = 0; i < num; i++) { + c = (V[i].e[0].size() + V[i].e[1].size()); + if (c > 2) + B += 1; + } + + return B; + } + + // get the number of starting or ending points in the network + size_t EndP() { + size_t B = 0; + size_t c; + size_t num = V.size(); + for (size_t i = 0; i < num; i++) { + c = (V[i].e[0].size() + V[i].e[1].size()); + if (c == 1) + B += 1; + } + + return B; + } + + // get an average of fiber length in the network + T Lengths() { + std::vector L; + T sumLength = 0.0f; + size_t num = E.size(); + for (size_t i = 0; i < num; i++) { + L.push_back(E[i].length()); + sumLength += E(i).length(); + } + T avg = sumLength / (T)num; + + return avg; + } + + // get the total number of points in the network + size_t total_points() { + size_t n = 0; + size_t num = E.size(); + for (size_t i = 0; i < num; i++) + n += num; + + return n; + } + + // get an average of tortuosities in the network + T Tortuosities() { + std::vector t; + std::vector id1, id2; + T distance; + T tortuosity; + T sumTortuosity = 0.0f; + size_t num = E.size(); + for (size_t i = 0; i < num; i++) { + id1 = E[i][0]; // get the starting point + id2 = E[i][num - 1]; // get the ending point + distance = (id1 - id2).len(); + if (distance > 0) + tortuosity = E[i].length() / distance; // tortuosity = edge length / edge displacement + else + tortuosity = 0.0f; + t.push_back(tortuosity); + sumTortuosity += tortuosity; + } + T avg = sumTortuosity / (T)num; + + return avg; + } + + // get an average contraction of the network + T Contraction() { + std::vector t; + std::vector id1, id2; // starting and ending vertices of the edge + T distance; + T contraction; + T sumContraction = 0.0f; + size_t num = E.size(); + for (size_t i = 0; i < num; i++) { // for each edge in the network + id1 = E[i][0]; // get the edge starting point + id2 = E[i][num - 1]; // get the edge ending point + distance = (id1 - id2).len(); // displacement between the starting and ending points + contraction = distance / E[i].length(); // contraction = edge displacement / edge length + t.push_back(contraction); + sumContraction += contraction; + } + T avg = sumContraction / (T)num; + + return avg; + } + + // get an average fractal dimension of the branches of the network + T FractalDimensions() { + std::vector t; + std::vector id1, id2; // starting and ending vertices of the edge + T distance; + T fract; + T sumFractDim = 0.0f; + size_t num = E.size(); + for (size_t i = 0; i < num; i++) { // for each edge in the network + id1 = E[i][0]; // get the edge starting point + id2 = E[i][num - 1]; // get the edge ending point + distance = (id1 - id2).len(); // displacement between the starting and ending points + fract = std::log(distance) / std::log(E[i].length()); // fractal dimension = log(edge displacement) / log(edge length) + t.push_back(sumFractDim); + sumFractDim += fract; + } + T avg = sumFractDim / (T)num; + + return avg; + } + + + /// construct network from files + // load network from OBJ files + void load_obj(std::string filename) { + stim::obj O; // create OBJ object + O.load(filename); // load OBJ file to an object + + size_t ii[2]; // starting/ending point index of one centerline/edge + std::vector index; // added vertex index + std::vector::iterator it; // iterator for searching + size_t pos; // position of added vertex + + // get the points + for (size_t l = 1; l <= O.numL(); l++) { // for every line of points + std::vector > tmp; // temp centerline + O.getLine(l, tmp); // get points + size_t n = tmp.size(); + std::vector > c(n); + for (size_t i = 0; i < n; i++) { // switch from vec to vec3 + for (size_t j = 0; j < 3; j++) { + c[i][j] = tmp[i][j]; + } + } + + centerline C(c); // construct centerline + edge new_edge(C); // construct edge without radii + + std::vector id; // temp point index + O.getLinei(l, id); // get point index + + ii[0] = (size_t)id.front(); + ii[1] = (size_t)id.back(); + + size_t num = new_edge.size(); // get the number of point on current edge + + // for starting point + it = std::find(index.begin(), index.end(), ii[0]); + if (it == index.end()) { // new vertex + vertex new_vertex = new_edge[0]; + new_vertex.e[0].push_back(E.size()); // push back the outgoing edge index + new_edge.v[0] = V.size(); // save the starting vertex index + V.push_back(new_vertex); + index.push_back(ii[0]); // save the point index + } + else { // already added vertex + pos = std::distance(index.begin(), it); // find the added vertex position + V[pos].e[0].push_back(E.size()); // push back the outgoing edge index + new_edge.v[0] = pos; + } + + // for ending point + it = std::find(index.begin(), index.end(), ii[1]); + if (it == index.end()) { // new vertex + vertex new_vertex = new_edge[num - 1]; + new_vertex.e[1].push_back(E.size()); // push back the incoming edge index + new_edge.v[1] = V.size(); // save the ending vertex index + V.push_back(new_vertex); + index.push_back(ii[1]); // save the point index + } + else { // already added vertex + pos = std::distance(index.begin(), it); // find the added vertex position + V[pos].e[1].push_back(E.size()); // push back the incoming edge index + new_edge.v[1] = pos; + } + + E.push_back(new_edge); + } + + // get the radii + if (O.numVT()) { // copy radii information if provided + std::vector id; // a list stores the indices of points + for (size_t i = 1; i <= O.numL(); i++) { + id.clear(); // clear up temp for current round computation + O.getLinei(i, id); + size_t num = id.size(); // get the number of points + T radius; + for (size_t j = 0; j < num; j++) { + radius = O.getVT(id[j] - 1)[0] / 2; // hard-coded: radius = diameter / 2 + set_r(i - 1, j, radius); // copy the radius + } + } + } + } + + // load network from SWC files (neuron) + void load_swc(std::string filename) { + stim::swc S; // create a SWC object + S.load(filename); // load data from SWC file to an object + S.create_tree(); // link nodes according to connectivity as a tree + S.resample(); + + size_t i[2]; // starting/ending point index of one centerline/edge + std::vector index; // added vertex index + std::vector::iterator it; // iterator for searching + size_t pos; // position of added vertex + + for (size_t l = 0; l < S.numE; l++) { + std::vector > c; // temp centerline + S.get_points(l, c); + + centerline C(c); // construct centerline + + std::vector radius; + S.get_radius(l, radius); // get radius + + edge new_edge(C, radius); // construct edge + size_t num = new_edge.size(); // get the number of point on current edge + + i[0] = S.E[l].front(); + i[1] = S.E[l].back(); + + // for starting point + it = std::find(index.begin(), index.end(), i[0]); + if (it == index.end()) { // new vertex + vertex new_vertex = new_edge[0]; + new_vertex.e[0].push_back(E.size()); // push back the outgoing edge index + new_edge.v[0] = V.size(); // save the starting vertex index + V.push_back(new_vertex); + index.push_back(i[0]); // save the point index + } + else { // already added vertex + pos = std::distance(index.begin(), it); // find the added vertex position + V[pos].e[0].push_back(E.size()); // push back the outgoing edge index + new_edge.v[0] = pos; + } + + // for ending point + it = std::find(index.begin(), index.end(), i[1]); + if (it == index.end()) { // new vertex + vertex new_vertex = new_edge[num - 1]; + new_vertex.e[1].push_back(E.size()); // push back the incoming edge index + new_edge.v[1] = V.size(); // save the ending vertex index + V.push_back(new_vertex); + index.push_back(i[1]); // save the point index + } + else { // already added vertex + pos = std::distance(index.begin(), it); // find the added vertex position + V[pos].e[1].push_back(E.size()); // push back the incoming edge index + new_edge.v[1] = pos; + } + + E.push_back(new_edge); + } + } + +/* + // load a network in text file to a network class + void load_txt(std::string filename) { + std::vector file_contents; + std::ifstream file(filename.c_str()); + std::string line; + std::vector id2vert; // this list stores the vertex ID associated with each network vertex + // for each line in the text file, store them as strings in file_contents + while (std::getline(file, line)) { + std::stringstream ss(line); + file_contents.push_back(ss.str()); + } + size_t numEdges = atoi(file_contents[0].c_str()); // number of edges in the network + size_t I = atoi(file_contents[1].c_str()); // calculate the number of points3d on the first edge + size_t count = 1; size_t k = 2; // count is global counter through the file contents, k is for the vertices on the edges + + for (size_t i = 0; i < numEdges; i++) { + // pre allocate a position vector p with number of points3d on the edge p + std::vector > p(0, I); + // for each point on the nth edge + for (size_t j = k; j < I + k; j++) { + // split the points3d of floats with separator space and form a float3 position vector out of them + p.push_back(std::split(file_contents[j], ' ')); + } + count += p.size() + 1; // increment count to point at the next edge in the network + I = atoi(file_contents[count].c_str()); // read in the points3d at the next edge and convert it to an integer + k = count + 1; + edge new_edge = p; // create an edge with a vector of points3d on the edge + E.push_back(new_edge); // push the edge into the network + } + size_t numVertices = atoi(file_contents[count].c_str()); // this line in the text file gives the number of distinct vertices + count = count + 1; // this line of text file gives the first verrtex + + for (size_t i = 0; i < numVertices; i++) { + vertex new_vertex = std::split(file_contents[count], ' '); + V.push_back(new_vertex); + count += atoi(file_contents[count + 1].c_str()) + 2; // Skip number of edge ids + 2 to point to the next vertex + } + } +*/ + // load network from NWT files + void load_nwt(std::string filename) { + int dims[2]; // number of vertex, number of edges + read_nwt_header(filename, &dims[0]); // read header + std::ifstream file; + file.open(filename.c_str(), std::ios::in | std::ios::binary); // skip header information. + file.seekg(14 + 58 + 4 + 4, file.beg); + vertex v; + for (int i = 0; i < dims[0]; i++) { // for every vertex, read vertex, add to network. + file >> v; + std::cerr << v.str() << std::endl; + V.push_back(v); + } + + std::cout << std::endl; + for (int i = 0; i < dims[1]; i++) { // for every edge, read edge, add to network. + edge e; + file >> e; + std::cerr << e.str() << std::endl; + E.push_back(e); + } + file.close(); + } + + // save network to NWT files + void save_nwt(std::string filename) { + write_nwt_header(filename); + std::ofstream file; + file.open(filename.c_str(), std::ios::out | std::ios::binary | std::ios::app); ///since we have written the header we are not appending. + for (int i = 0; i < V.size(); i++) { // look through the Vertices and write each one. + file << V[i]; + } + for (int i = 0; i < E.size(); i++) { // loop through the Edges and write each one. + file << E[i]; + } + file.close(); + } + + /// NWT format functions + void read_nwt_header(std::string filename, int *dims) { + char magicString[14]; // id + char desc[58]; // description + int hNumVertices; // #vert + int hNumEdges; // #edges + std::ifstream file; // create stream + file.open(filename.c_str(), std::ios::in | std::ios::binary); + file.read(reinterpret_cast(&magicString[0]), 14); // read the file id. + file.read(reinterpret_cast(&desc[0]), 58); // read the description + file.read(reinterpret_cast(&hNumVertices), sizeof(int)); // read the number of vertices + file.read(reinterpret_cast(&hNumEdges), sizeof(int)); // read the number of edges + file.close(); // close the file. + dims[0] = hNumVertices; // fill the returned reference. + dims[1] = hNumEdges; + } + void write_nwt_header(std::string filename) { + std::string magicString = "nwtFileFormat "; // identifier for the file. + std::string desc = "fileid(14B), desc(58B), #vertices(4B), #edges(4B): bindata"; + int hNumVertices = V.size(); // int byte header storing the number of vertices in the file + int hNumEdges = E.size(); // int byte header storing the number of edges. + std::ofstream file; + file.open(filename.c_str(), std::ios::out | std::ios::binary); + std::cout << hNumVertices << " " << hNumEdges << std::endl; + file.write(reinterpret_cast(&magicString.c_str()[0]), 14); // write the file id + file.write(reinterpret_cast(&desc.c_str()[0]), 58); // write the description + file.write(reinterpret_cast(&hNumVertices), sizeof(int)); // write #vert. + file.write(reinterpret_cast(&hNumEdges), sizeof(int)); // write #edges + file.close(); + } + + // output the network as a string + std::string str() { + std::stringstream ss; + size_t nv = V.size(); + size_t ne = E.size(); + ss << "Node (" << nv << ")--------" << std::endl; + for (size_t i = 0; i < nv; i++) + ss << "\t" << i << V[i].str() << std::endl; + ss << "Edge (" << ne << ")--------" << std::endl; + for (size_t i = 0; i < ne; i++) + ss << "\t" << i << E[i].str() << std::endl; + + return ss.str(); + } + + // get a string of edges + std::string strTxt(std::vector > p) { + std::stringstream ss; + std::stringstream oss; + size_t num = p.size(); + for (size_t i = 0; i < p; i++) { + ss.str(std::string()); + for (size_t j = 0; j < 3; j++) + ss << p[i][j]; + ss << "\n"; + } + + return ss.str(); + } + + // removes specified character from string + void removeCharsFromString(std::string &str, char* charsToRemove) { + for (size_t i = 0; i < strlen(charsToRemove); i++) + str.erase((remove(str.begin(), str.end(), charsToRemove[i])), str.end()); + } + + // exports network to txt file + void to_txt(std::string filename) { + std::ofstream ofs(filename.c_str(), std::ofstream::out | std::ofstream::app); + + ofs << (E.size()).str() << "\n"; + for (size_t i = 0; i < E.size(); i++) { + std::string str; + ofs << (E[i].size()).str() << "\n"; + str = E[i].strTxt(); + ofs << str << "\n"; + } + for (size_t i = 0; i < V.size(); i++) { + std::string str; + str = V[i].str(); + char temp[4] = "[],"; + removeCharsFromString(str, temp); + ofs << str << "\n"; + } + ofs.close(); + } + + + /// advanced operations + // adding a fiber to current network + // prior information: attaching fiber "f" and point "p" information, if "order" = 0 means the first point on fiber "e" is the attaching one (others mean the last point) + // "add" = 0 means replace the point on fiber "e" which is to be attached to the point on current fiber, "add" = 1 means add that one. Default "add" = 0 + // ********** we don't accept that one fiber has only 3 points on it, reorder if this happens ********** + void add_fiber(edge e, size_t f, size_t p, size_t order, size_t add = 0) { + size_t num = E[f].size(); // get the number of points on this fiber + size_t num1 = p + 1; // first "half" + size_t num2 = num - p; // second "half" + size_t id = p; // split point on the fiber that attach to + size_t ne = e.size(); + + // if a new fiber only has points that less than 4, either add or change (default) an attaching point + if (num1 < 4) + id = 0; + else if (num2 < 4) + id = num - 1; + + // check to see whether it needs to replace/add the joint point + if (add == 0) { // change the point + if (order == 0) { + e[0] = E[f][id]; + e.set_r(0, r(f, id)); + } + else { + e[ne - 1] = E[f][id]; + e.set_r(ne - 1, r(f, id)); + } + } + else { // add a point if necessary + if (order == 0) { + if (e[0] == E[f][id]) // if they share the same joint point, make sure radii are consistent + e.set_r(0, r(f, id)); + else + e.insert(0, E[f][id], r(f, id)); + } + else { + if (e[ne - 1] == E[f][id]) + e.set_r(ne - 1, r(f, id)); + else + e.insert(ne - 1, E[f][id], r(f, id)); + } + } + + std::vector tmp_edge = E[f].split(id); // check and split + // current one hasn't been splitted + if (tmp_edge.size() == 1) { + if (id == 0) { // stitch location is the starting point of current edge + if (V[E[f].v[0]].e[0].size() + V[E[f].v[0]].e[1].size() > 1) { // branching point + if (order == 0) { + V[E[f].v[0]].e[0].push_back(E.size()); + edge new_edge(e); + new_edge.v[0] = E[f].v[0]; // set the starting and ending points for the new edge + new_edge.v[1] = V.size(); + E.push_back(new_edge); + vertex new_vertex = e[e.size() - 1]; + new_vertex.e[1].push_back(V.size()); // set the incoming edge for the new point + V.push_back(new_vertex); + } + else { + V[E[f].v[0]].e[1].push_back(E.size()); + edge new_edge(e); + new_edge.v[0] = V.size(); // set the starting and ending points for the new edge + new_edge.v[1] = E[f].v[0]; + E.push_back(new_edge); + vertex new_vertex = e[e.size() - 1]; + new_vertex.e[0].push_back(V.size()); // set the outgoing edge for the new point + V.push_back(new_vertex); + } + } + else { // not branching point + size_t k = E[f].v[0]; // get the index of the starting point on current edge + vertex new_vertex; + edge new_edge; + if (order == 0) { + new_vertex = e[e.size() - 1]; + new_edge = E[f].concatenate(e, 0, 0); + } + else { + new_vertex = e[0]; + new_edge = E[f].concatenate(e, 0, e.size() - 1); + } + new_vertex.e[0].push_back(f); + new_edge.v[1] = E[f].v[1]; // set starting and ending points for the new concatenated edge + new_edge.v[0] = E[f].v[0]; + V[k] = new_vertex; + E[f] = new_edge; + } + } + else { // stitch location is the ending point of current edge + if (V[E[f].v[1]].e[0].size() + V[E[f].v[1]].e[1].size() > 1) { // branching point + if (order == 0) { + V[E[f].v[1]].e[0].push_back(E.size()); + edge new_edge(e); + new_edge.v[0] = E[f].v[1]; // set the starting and ending points for the new edge + new_edge.v[1] = V.size(); + E.push_back(new_edge); + vertex new_vertex = e[e.size() - 1]; + new_vertex.e[1].push_back(V.size()); // set the incoming edge for the new point + V.push_back(new_vertex); + } + else { + V[E[f].v[1]].e[1].push_back(E.size()); + edge new_edge(e); + new_edge.v[0] = V.size(); // set the starting and ending points for the new edge + new_edge.v[1] = E[f].v[1]; + E.push_back(new_edge); + vertex new_vertex = e[e.size() - 1]; + new_vertex.e[0].push_back(V.size()); // set the outgoing edge for the new point + V.push_back(new_vertex); + } + } + else { // not branching point + size_t k = E[f].v[1]; // get the index of the ending point on current edge + vertex new_vertex; + edge new_edge; + if (order == 0) { + new_vertex = e[e.size() - 1]; + new_edge = E[f].concatenate(e, num - 1, 0); + } + else { + new_vertex = e[0]; + new_edge = E[f].concatenate(e, num - 1, e.size() - 1); + } + new_vertex.e[1].push_back(f); + new_edge.v[1] = E[f].v[1]; // set starting and ending points for the new concatenated edge + new_edge.v[0] = E[f].v[0]; + V[k] = new_vertex; + E[f] = new_edge; + } + } + } + // current one has been splitted + else { + vertex new_vertex = E[f][id]; + V.push_back(new_vertex); + tmp_edge[0].v[0] = E[f].v[0]; + tmp_edge[0].v[1] = V.size() - 1; // set the ending point of the first half edge + tmp_edge[1].v[0] = V.size() - 1; // set the starting point of the second half edge + tmp_edge[1].v[1] = E[f].v[1]; + edge tmp(E[f]); + E[f] = tmp_edge[0]; // replace current edge by the first half edge + E.push_back(tmp_edge[1]); + V[V.size() - 1].e[0].push_back(E.size() - 1); // set the incoming and outgoing edges for the splitted point + V[V.size() - 1].e[1].push_back(f); // push "f" fiber as an incoming edge for the splitted point + for (size_t i = 0; i < V[tmp.v[1]].e[1].size(); i++) // set the incoming edge for the original ending vertex + if (V[tmp.v[1]].e[1][i] == f) + V[tmp.v[1]].e[1][i] = E.size() - 1; + + if (order == 0) { + e.v[0] = V.size() - 1; // set the starting and ending points for the new edge + e.v[1] = V.size(); + V[V.size() - 1].e[0].push_back(E.size()); // we assume "flow" flows from starting point to ending point! + new_vertex = e[e.size() - 1]; // get the ending point on the new edge + E.push_back(e); + V.push_back(new_vertex); + V[V.size() - 1].e[1].push_back(E.size() - 1); + } + else { + e.v[0] = V.size(); // set the starting and ending points for the new edge + e.v[1] = V.size() - 1; + V[V.size() - 1].e[1].push_back(E.size()); + new_vertex = e[0]; // get the ending point on the new edge + E.push_back(e); + V.push_back(new_vertex); + V[V.size() - 1].e[0].push_back(E.size() - 1); + } + } + } + + // THIS IS FOR PAVEL + // @param "e" is the edge that is to be stitched + // @param "f" is the index of edge that is to be stiched to + void stitch(edge e, size_t f) { + network A = (*this); // make a copy of current network + + T* query_point = new T[3]; + for (size_t k = 0; k < 3; k++) + query_point[k] = e[0][k]; // we assume the first one is the one to be stitched + + size_t num = A.E[f].size(); // get the number of points on edge "f" + T* reference_point = (T*)malloc(sizeof(T) * num * 3); + A.E[f].edge_to_array(reference_point); + size_t max_tree_level = 3; + + stim::kdtree kdt; // initialize a tree + kdt.create(reference_point, num, max_tree_level); // build a tree + + T* dist = new T[1]; + size_t* nnIdx = new size_t[1]; + +#ifdef __CUDACC__ + kdt.search(query_point, 1, nnIdx, dist); // search for nearest neighbor +#else + kdt.cpu_search(query_point, 1, nnIdx, dist);// search for nearest neighbor +#endif + add_fiber(e, f, nnIdx[0], 0, 1); + + free(reference_point); + delete(dist); + delete(nnIdx); + } + + // split current network at "idx" location on edge "f" + network split(size_t f, size_t idx) { + size_t num = E.size(); + + if (f >= num) { // outside vector size + } + else { + if (idx <= 0 || idx >= num - 1) { // can't split at this position + } + else { + std::vector list; // a list of edges + list = E[f].split(idx); // split in tems of edges + // first segment replaces original one + edge new_edge = list[0]; // new edge + edge tmp(E[f]); // temp edge + new_edge.v[0] = E[f].v[0]; // copy starting point + new_edge.v[1] = V.size(); // set ending point + E[f] = new_edge; // replacement + vertex new_vertex(new_edge[idx]); + new_vertex.e[1].push_back(f); // incoming edge for the new vertex + new_vertex.e[0].push_back(E.size()); // outgoing edge for the new vertex + + // second segment gets newly push back + new_edge = list[1]; // new edge + new_edge.v[1] = tmp.v[1]; // copy ending point + new_edge.v[0] = V.size(); // set starting point + size_t n = V[tmp.v[1]].e[1].size(); + for (size_t i = 0; i < n; i++) { + if (V[tmp.v[1]].e[1][i] == f) { + V[tmp.v[1]].e[1][i] = E.size(); + break; + } + } + + V.push_back(new_vertex); + E.push_back(new_edge); + } + } + + return (*this); + } + + // resample current network + network resample(T spacing) { + stim::network result; + + result.V = V; // copy vertices + size_t num = E.size(); // get the number of edges + result.E.resize(num); + + for (size_t i = 0; i < num; i++) + result.E[i] = E[i].resample(spacing); + + return result; + } + + // copy the point cload representing the centerline for the network into an array + void centerline_cloud(T* dst) { + size_t p; // store the current edge point + size_t P; // store the number of points in an edge + size_t t = 0; // index in to the output array of points + size_t num = E.size(); + for (size_t i = 0; i < num; i++) { + P = E[i].size(); + for (p = 0; p < P; p++) { + dst[t * 3 + 0] = E[i][p][0]; + dst[t * 3 + 1] = E[i][p][1]; + dst[t * 3 + 2] = E[i][p][2]; + t++; + } + } + } + + // subdivide current network and represent as a explicit undirected graph + network to_graph() { + std::vector OI; // a list of original vertex index + std::vector NI; // a list of new vertex index + std::vector nE; // a list of new edge + std::vector nV; // a list of new vector + size_t id = 0; // temp vertex index + size_t n = E.size(); + + for (size_t i = 0; i < n; i++) { + if (E[i].size() == 2) { // if current edge only contain 2 points, can't be subdivided + stim::centerline line; + for (size_t k = 0; k < 2; k++) // copy points on current network + line.push_back(E[i][k]); + + edge new_edge(line); // construct edge based on current centerline + + for (size_t k = 0; k < 2; k++) { + vertex new_vertex = new_edge[k]; + id = E[i].v[k]; // get the starting/ending point index + std::vector::iterator pos = std::find(OI.begin(), OI.end(), id); // search this index through the list of new vertex index and see whether it appears + if (pos == OI.end()) { // current vertex hasn't been pushed back + OI.push_back(id); // add current vertex to the original list + NI.push_back(nV.size()); // add new index + + new_vertex.e[k].push_back(nE.size()); // set outgoing/incoming edge for the new vertex + new_edge.v[k] = nV.size(); // set the starting/ending vertex for the new edge + nV.push_back(new_vertex); + } + else { // current vertex has been pushed back before + int d = std::distance(OI.begin(), pos); + new_edge.v[k] = NI[d]; + nV[NI[d]].e[k].push_back(nE.size()); + } + } + + nE.push_back(new_edge); + + // copy radii information + nE[nE.size() - 1].set_r(0, E[i].r(0)); + nE[nE.size() - 1].set_r(1, E[i].r(1)); + } + else { // if current edge contain at least 3 points, can be subdivided + size_t num = E[i].size(); + for (size_t j = 0; j < num - 1; j++) { + stim::centerline line; + for (size_t k = 0; k < 2; k++) // copy points on current network + line.push_back(E[i][k]); + + edge new_edge(line); + if (j == 0) { // for edge that contains original starting point + vertex new_vertex = new_edge[0]; + id = E[i].v[0]; + std::vector::iterator pos = std::find(OI.begin(), OI.end(), id); + if (pos == OI.end()) { // new vertex + OI.push_back(id); + NI.push_back(nV.size()); + + new_vertex.e[0].push_back(nE.size()); + new_edge.v[0] = nV.size(); + nV.push_back(new_vertex); + } + else { + int d = std::distance(OI.begin(), pos); + new_edge.v[0] = NI[d]; + nV[NI[d]].e[0].push_back(nE.size()); + } + + new_vertex = new_edge[1]; + new_vertex.e[1].push_back(nE.size()); + new_edge.v[1] = nV.size(); + nV.push_back(new_vertex); + nE.push_back(new_edge); + } + + else if (j == E[i].size() - 2) {// for edge that contains original ending point + vertex new_vertex = new_edge[1]; + nV[nV.size() - 1].e[0].push_back(nE.size()); + new_edge.v[0] = nV.size() - 1; + + id = E[i].v[1]; // get ending vertex index + std::vector::iterator pos = std::find(OI.begin(), OI.end(), id); + if (pos == OI.end()) { + OI.push_back(id); + NI.push_back(nV.size()); + + new_vertex.e[1].push_back(nE.size()); + new_edge.v[1] = nV.size(); + nV.push_back(new_vertex); + } + else { + int d = std::distance(OI.begin(), pos); + new_edge.v[1] = NI[d]; + nV[NI[d]].e[1].push_back(nE.size()); + } + + nE.push_back(new_edge); + } + + else { // for edge that doesn't contain original ending point + vertex new_vertex = new_edge[1]; + + // push back the latter one on that segment + nV[nV.size() - 1].e[0].push_back(nE.size()); + new_vertex.e[1].push_back(nE.size()); + new_edge.v[0] = nV.size() - 1; + new_edge.v[1] = nV.size(); + nV.push_back(new_vertex); + nE.push_back(new_edge); + } + + // copy radii information + nE[nE.size() - 1].set_r(0, E[i].r(j)); + nE[nE.size() - 1].set_r(1, E[i].r(j + 1)); + } + } + } + + stim::network result(nE, nV); + + return result; + } + + // this function compares two networks and returns the percentage of the current network that is missing from "A" + // @param "A" is the network to compare to - the field is generated for A + // @param "sigma" is the user-defined tolerance value - smaller values provide a stricter comparison + // @param "device" is the GPU device to use - default no GPU provided + network compare(network A, T sigma, int device = -1) { + network R; // generate a network storing the result of comparison + R = (*this); // initialize to current network + + size_t num = A.total_points(); + T* c = (T*)malloc(sizeof(T) * num * 3); // allocate memory for the centerline of A + + A.copy_to_array(c); // copy points in A to a 1D array + + size_t max_tree_level = 3; // set max tree level parameter to 3 + +#ifdef __CUDACC__ + cudaSetDevice(device); + stim::kdtree kdt; // initialize a tree object + + kdt.create(c, num, max_tree_level); // build tree + + for (size_t i = 0; i < R.E.size(); i++) { + size_t n = R.E[i].size(); // the number of points in current edge + T* query_point = new T[3 * n]; // allocate memory for points + T* m1 = new T[n]; // allocate memory for metrics + T* dists = new T[n]; // allocate memory for distances + size_t* nnIdx = new size_t[n]; // allocate memory for indices + + T* d_dists; + T* d_m1; + cudaMalloc((void**)&d_dists, n * sizeof(T)); + cudaMalloc((void**)&d_m1, n * sizeof(T)); + + edge_to_array(query_point, R.E[i]); + kdt.search(query_point, n, nnIdx, dists); + + cudaMemcpy(d_dists, dists, n * sizeof(T), cudaMemcpyHostToDevice); // copy dists from host to device + + // configuration parameters + size_t threads = (1024 > n) ? n : 1024; + size_t blocks = n / threads + (n % threads) ? 1 : 0; + + find_metric<< > >(d_m1, n, d_dists, sigma); // calculate the metric value based on the distance + + cudaMemcpy(m1, d_m1, n * sizeof(T), cudaMemcpyDeviceToHost); + + for (size_t j = 0; j < n; j++) { + R.E[i].set_r(j, m1[j]); + } + } + +#else // if there is any GPU device, use CPU - much slower + stim::kdtree kdt; + kdt.create(c, num, max_tree_level); + + for (size_t i = 0; i < R.E.size(); i++) { // for each edge in A + + size_t n = R.E[i].size(); // the number of points in current edge + T* query = new T[3 * n]; + T* m1 = new T[n]; + T* dists = new T[n]; + size_t* nnIdx = new size_t[n]; + + edge_to_array(query, R.E[i]); + + kdt.cpu_search(query, n, nnIdx, dists); // find the distance between A and the current network + + for (size_t j = 0; j < n; j++) { + m1[j] = 1.0f - gaussian(dists[j], sigma); // calculate the metric value based on the distance + R.E[i].set_r(j, m1[j]); // set the error for the second point in the segment + } + } +#endif + return R; + } + + // this function compares two splitted networks to yield a mapping relationship between them according to nearest neighbor principle + // @param "B" is the template network + // @param "C" is the mapping relationship: C[e1] = _e1 means edge "e1" in current network is mapped to edge "_e1" in "B" + // @param "device" is the GPU device that user want to use + // @param "threshold" is to control mapping tolerance (threshold) + void mapping(network B, std::vector &C, T threshold, int device = -1) { + network A; // generate a network storing the result of the comparison + A = (*this); + + size_t nA = A.E.size(); // the number of edges in A + size_t nB = B.E.size(); // the number of edges in B + + C.resize(A.E.size()); + + size_t num = B.total_points(); // set the number of points + T* c = (T*)malloc(sizeof(T) * num * 3); + + B.copy_to_array(c); + + size_t max_tree_level = 3; + +#ifdef __CUDACC__ + cudaSetDevice(device); + stim::kdtree kdt; // initialize a tree + + kdt.create(c, num, max_tree_level); // build a tree + + T M = 0.0f; + for (size_t i = 0; i < nA; i++) { + M = A.ar(i); // get the average metric of edge "i" + if (M > threshold) + C[i] = UINT_MAX; // set to MAX + else { + T* query_point = new T[3]; + T* dist = new T[1]; + size_t* nnIdx = new size_t[1]; + + for (size_t k = 0; k < 3; k++) + query_point[k] = A.E[i][A.E[i].size() / 2][k]; // search by the middle one, risky? + kdt.search(query_point, 1, nnIdx, dist); + + size_t id = 0; + size_t sum = 0; + for (size_t j = 0; j < nB; j++) { // low_band + sum += B.E[j].size(); + if (nnIdx[0] < sum) { + C[i] = id; + break; + } + id++; + } + } + } + +#else + stim::kdtree kdt; + kdt.create(c, num, max_tree_level); + T* dist = new T[1]; + size_t* nnIdx = new size_t[1]; + + stim::vec3 p; + T* query_point = new T[3]; + + T M = 0.0f; + for (size_t i = 0; i < nA; i++) { + M = A.ar(i); + if (M > threshold) + C[i] = UINT_MAX; + else { + p = A.E[i][A.E[i].size() / 2]; + for (size_t k = 0; k < 3; k++) + query_point[k] = p[k]; + kdt.cpu_search(query_point, 1, nnIdx, dist); + + size_t id = 0; + size_t sum = 0; + for (size_t j = 0; j < nB; j++) { + sum += B.E[j].size(); + if (nnIdx[0] < sum) { + C[i] = id; + break; + } + id++; + } + } + } +#endif + } + }; + +} + + +#endif diff --git a/tira/biomodels/network_dep.h b/tira/biomodels/network_dep.h new file mode 100644 index 0000000..c9a1ee3 --- /dev/null +++ b/tira/biomodels/network_dep.h @@ -0,0 +1,468 @@ +/* +Copyright <2017> + +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. +*/ +#ifndef STIM_NETWORK_H +#define STIM_NETWORK_H + +#include +#include +#include +//#include + +namespace stim{ + +/** This class provides an interface for dealing with biological networks. + * It takes the following aspects into account: + * 1) Network geometry and centerlines + * 2) Network connectivity (a graph structure can be extracted) + * 3) Network surface structure (the surface is represented as a triangular mesh and referenced to the centerline) + */ + +template +class network{ + + //-------------------HELPER CLASSES----------------------- + /// Stores information about a geometric point on the network centerline (including point position and radius) + //template + class point : public stim::vec{ + + public: + T r; + + point() : stim::vec(){} + + //casting constructor + point(stim::vec rhs) : stim::vec(rhs){} + }; + + //template + class t_node; + class fiber; + + //create typedefs for the iterators to simplify the network code + typedef typename std::list< fiber >::iterator fiber_i; + typedef typename std::list< t_node >::iterator t_node_i; + + /// Stores information about a single capillary (a length of vessel between two branch or end points) + //template + class fiber : public std::list< point >{ + + using std::list< point >::begin; + using std::list< point >::end; + using std::list< point >::size; + + + public: + //std::list< point > P; //geometric point positions + + typename std::list< t_node >::iterator n[2]; //indices to terminal nodes + unsigned int id; + + public: + + /// Calculate the length of the fiber and return it. + T length(){ + + point p0, p1; + T l = 0; //initialize the length to zero + + //for each point + typename std::list< point >::iterator i; //create a point iterator + for(i = begin(); i != end(); i++){ //for each point in the fiber + + if(i == begin()) //if this is the first point, just store it + p1 = *i; + else{ //if this is any other point + p0 = p1; //shift p1->p0 + p1 = *i; //set p1 to the new point + l += (p1 - p0).len(); //add the length of p1 - p0 to the running sum + } + } + + return l; //return the length + } + + T radius(T& length){ + + point p0, p1; //temporary variables to store point positions + T r0, r1; //temporary variables to store radii at points + T l, r; //temporary variable to store the length and average radius of a fiber segment + T length_sum = 0; //initialize the length to zero + T radius_sum = 0; //initialize the radius sum to zero + + //for each point + typename std::list< point >::iterator i; //create a point iterator + for(i = begin(); i != end(); i++){ //for each point in the fiber + + if(i == begin()){ //if this is the first point, just store it + p1 = *i; + r1 = i->r; + } + else{ //if this is any other point + p0 = p1; //shift p1->p0 and r1->r0 + r0 = r1; + p1 = *i; //set p1 to the new point + r1 = i->r; //and r1 + + l = (p1 - p0).len(); //calculate the length of the p0-p1 segment + r = (r0 + r1) / 2; //calculate the average radius of the segment + + radius_sum += r * l; //add the radius scaled by the length to a running sum + length_sum += l; //add the length of p1 - p0 to the running sum + } + } + + length = length_sum; //store the total length + + //if the total length is zero, store a radius of zero + if(length == 0) + return 0; + else + return radius_sum / length; //return the average radius of the fiber + } + + std::vector< stim::vec > geometry(){ + + std::vector< stim::vec > result; //create an array to store the fiber geometry + result.resize( size() ); //pre-allocate the array + + typename std::list< point >::iterator p; //create a list iterator + unsigned int pi = 0; //create an index into the result array + + //for each geometric point on the fiber centerline + for(p = begin(); p != end(); p++){ + result[pi] = *p; + pi++; + } + + return result; //return the geometry array + + } + + std::string str(){ + std::stringstream ss; + + //create an iterator for the point list + typename std::list::iterator i; + for(i = begin(); i != end(); i++){ + ss<str()<<" r = "<r< + class t_node{ + + public: + + unsigned int id; + + //lists of edge indices for capillaries + //the "in" and "out" just indicate how the geometry is defined: + // edges in the "in" list are geometrically oriented such that the terminal node is last + // edges in the "out" list are geometrically oriented such that the terminal node is first + std::list< fiber_i > in; //edge indices for incoming capillaries + std::list< fiber_i > out; //edge indices for outgoing capillaries + + std::string str(){ + + std::stringstream ss; + + ss<::iterator f; + + for(f = in.begin(); f != in.end(); f++){ + + if(f != in.begin()) + ss<<", "; + ss<<(*f)->n[0]->id; + } + + //if there are nodes in both lists, separate them by a comma + if(out.size() > 0 && in.size() > 0) + ss<<", "; + + for(f = out.begin(); f != out.end(); f++){ + + if(f != out.begin()) + ss<<", "; + ss<<(*f)->n[1]->id; + } + + + return ss.str(); + + + + } + }; + + +//---------------NETWORK CLASS----------------------------- + +protected: + + //list of terminal nodes + std::list N; + + //list of fibers + std::list F; + + /// Sets a unique ID for each terminal node and fiber + void set_names(){ + + unsigned int i; + + i = 0; + for(t_node_i ti = N.begin(); ti != N.end(); ti++) + ti->id = i++; + + i = 0; + for(fiber_i fi = F.begin(); fi != F.end(); fi++) + fi->id = i++; + } + +public: + + std::string str(){ + + + //assign names to elements of the network + set_names(); + + //create a stringstream for output + std::stringstream ss; + + //output the nodes + ss<<"Nodes-----------------------------------------------"<str()<radius(length); + + //output the IDs of the terminal nodes + ss<n[0]->id<<" -- "<n[1]->id<<": length = "< +//#include +#include +#include + +/// This function generates the first-order gaussian derivative filter gx gy, +/// convolves the image with gx gy, +/// and returns an image class which channel(0) is Ix and channel(1) is Iy + +/// @param img is the one-channel image +/// @param r is an array of radii for different scaled discs(filters) +/// @param sigma_n is the number of standard deviations used to define the sigma + +void conv2_sep(float* img, unsigned int x, unsigned int y, float* kernel0, unsigned int k0, float* kernel1, unsigned int k1); +//void array_abs(float* img, unsigned int N); + +stim::image Gd1(stim::image image, int r, unsigned int sigma_n){ + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + int winsize = 2 * r + 1; // set the winsdow size of disc(filter) + float sigma = float(r)/float(sigma_n); // calculate the sigma used in gaussian function + + stim::image I(w, h, 1, 2); // allocate space for return image class + stim::image Ix(w, h); // allocate space for Ix + stim::image Iy(w, h); // allocate space for Iy + Ix = image; // initialize Ix + Iy = image; // initialize Iy + + float* array_x1; + array_x1 = new float[winsize]; //allocate space for the 1D x-oriented gaussian derivative filter array_x1 for gx + float* array_y1; + array_y1 = new float[winsize]; //allocate space for the 1D y-oriented gaussian derivative filter array_y1 for gx + float* array_x2; + array_x2 = new float[winsize]; //allocate space for the 1D x-oriented gaussian derivative filter array_x2 for gy + float* array_y2; + array_y2 = new float[winsize]; //allocate space for the 1D y-oriented gaussian derivative filter array_y2 for gy + + + for (int i = 0; i < winsize; i++){ + + int x = i - r; //range of x + int y = i - r; //range of y + + // create the 1D x-oriented gaussian derivative filter array_x1 for gx + array_x1[i] = (-1) * x * exp((-1)*(pow(x, 2))/(2*pow(sigma, 2))); + // create the 1D y-oriented gaussian derivative filter array_y1 for gx + array_y1[i] = exp((-1)*(pow(y, 2))/(2*pow(sigma, 2))); + // create the 1D x-oriented gaussian derivative filter array_x2 for gy + array_x2[i] = exp((-1)*(pow(x, 2))/(2*pow(sigma, 2))); + // create the 1D y-oriented gaussian derivative filter array_y2 for gy + array_y2[i] = (-1) * y * exp((-1)*(pow(y, 2))/(2*pow(sigma, 2))); + } + + //stim::cpu2image(array_x1, "data_output/array_x1_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_y1, "data_output/array_y1_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_x2, "data_output/array_x2_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_y2, "data_output/array_y2_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + + // get Ix by convolving the image with gx + conv2_sep(Ix.data(), w, h, array_x1, winsize, array_y1, winsize); + + //stim::cpu2image(Ix.data(), "data_output/Ix_0915.bmp", w, h, stim::cmBrewer); + // get Iy by convolving the image with gy + conv2_sep(Iy.data(), w, h, array_x2, winsize, array_y2, winsize); + + //stim::cpu2image(Iy.data(), "data_output/Iy_0915.bmp", w, h, stim::cmBrewer); + + delete [] array_x1; //free the memory of array_x1 + delete [] array_y1; //free the memory of array_y1 + delete [] array_x2; //free the memory of array_x2 + delete [] array_y2; //free the memory of array_y2 + + I.set_channel(0, Ix.data()); + I.set_channel(1, Iy.data()); + + return I; + +} \ No newline at end of file diff --git a/tira/cuda/bsds500/dG1_theta_conv2.cpp b/tira/cuda/bsds500/dG1_theta_conv2.cpp new file mode 100644 index 0000000..b1ea9aa --- /dev/null +++ b/tira/cuda/bsds500/dG1_theta_conv2.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +#define PI 3.1415926 + +void array_multiply(float* lhs, float rhs, unsigned int N); +void array_add(float* ptr1, float* ptr2, float* sum, unsigned int N); +void array_abs(float* img, unsigned int N); + +/// This function evaluates the theta-dependent odd symmetric gaussian derivative gradient of an one-channel image + +/// @param img is the one-channel image +/// @param r is an array of radii for different scaled discs(filters) +/// @param sigma_n is the number of standard deviations used to define the sigma +/// @param theta is angle used for computing the gradient + +stim::image Gd_odd(stim::image image, int r, unsigned int sigma_n, float theta){ + + float theta_r = (theta * PI)/180; //change angle unit from degree to rad + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + int winsize = 2 * r + 1; // set the winsdow size of disc(filter) + + stim::image I(w, h, 1, 2); // allocate space for return image of Gd1 + stim::image Ix(w, h); // allocate space for Ix + stim::image Iy(w, h); // allocate space for Iy + stim::image Gd_odd_theta(w, h); // allocate space for Pb + + I = Gd1(image, r, sigma_n); // calculate the Ix, Iy + Ix = I.channel(0); + Iy = I.channel(1); + + array_multiply(Ix.data(), cos(theta_r), N); //Ix = Ix*cos(theta_r) + array_multiply(Iy.data(), sin(theta_r), N); //Iy = Iy*sin(theta_r) + array_add(Ix.data(), Iy.data(), Gd_odd_theta.data(), N); //Gd_odd_theta = Ix + Iy; + array_abs(Gd_odd_theta.data(), N); + + //stim::cpu2image(I.channel(0).data(), "data_output/Gd_odd_x_0919.bmp", w, h, stim::cmBrewer); + //stim::cpu2image(I.channel(1).data(), "data_output/Gd_odd_y_0919.bmp", w, h, stim::cmBrewer); + //stim::cpu2image(Gd_odd_theta.data(), "data_output/Gd_odd_theta_0919.bmp", w, h, stim::cmBrewer); + + return Gd_odd_theta; + +} diff --git a/tira/cuda/bsds500/dG2_conv2.cpp b/tira/cuda/bsds500/dG2_conv2.cpp new file mode 100644 index 0000000..85414b1 --- /dev/null +++ b/tira/cuda/bsds500/dG2_conv2.cpp @@ -0,0 +1,80 @@ +#include +//#include +#include +#include + +/// This function generates the second-order gaussian derivative filter gxx gyy, +/// convolves the image with gxx gyy, +/// and returns an image class which channel(0) is Ixx and channel(1) is Iyy + +/// @param img is the one-channel image +/// @param r is an array of radii for different scaled discs(filters) +/// @param sigma_n is the number of standard deviations used to define the sigma + +void conv2_sep(float* img, unsigned int x, unsigned int y, float* kernel0, unsigned int k0, float* kernel1, unsigned int k1); +//void array_abs(float* img, unsigned int N); + +stim::image Gd2(stim::image image, int r, unsigned int sigma_n){ + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + int winsize = 2 * r + 1; // set the winsdow size of disc(filter) + float sigma = float(r)/float(sigma_n); // calculate the sigma used in gaussian function + + stim::image I(w, h, 1, 2); // allocate space for return image class + stim::image Ixx(w, h); // allocate space for Ixx + stim::image Iyy(w, h); // allocate space for Iyy + Ixx = image; // initialize Ixx + Iyy = image; // initialize Iyy + + float* array_x1; + array_x1 = new float[winsize]; //allocate space for the 1D x-oriented gaussian derivative filter array_x1 for gxx + float* array_y1; + array_y1 = new float[winsize]; //allocate space for the 1D y-oriented gaussian derivative filter array_y1 for gxx + float* array_x2; + array_x2 = new float[winsize]; //allocate space for the 1D x-oriented gaussian derivative filter array_x2 for gyy + float* array_y2; + array_y2 = new float[winsize]; //allocate space for the 1D y-oriented gaussian derivative filter array_y2 for gyy + + + for (int i = 0; i < winsize; i++){ + + int x = i - r; //range of x + int y = i - r; //range of y + + // create the 1D x-oriented gaussian derivative filter array_x1 for gxx + array_x1[i] = (-1) * (1 - pow(x, 2)) * exp((-1)*(pow(x, 2))/(2*pow(sigma, 2))); + // create the 1D y-oriented gaussian derivative filter array_y1 for gxx + array_y1[i] = exp((-1)*(pow(y, 2))/(2*pow(sigma, 2))); + // create the 1D x-oriented gaussian derivative filter array_x2 for gyy + array_x2[i] = exp((-1)*(pow(x, 2))/(2*pow(sigma, 2))); + // create the 1D y-oriented gaussian derivative filter array_y2 for gyy + array_y2[i] = (-1) * (1 - pow(y, 2)) * exp((-1)*(pow(y, 2))/(2*pow(sigma, 2))); + } + + //stim::cpu2image(array_x1, "data_output/array_x1_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_y1, "data_output/array_y1_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_x2, "data_output/array_x2_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + //stim::cpu2image(array_y2, "data_output/array_y2_0915.bmp", winsize, 1, stim::cmBrewer); // (optional) show the mask result + + // get Ixx by convolving the image with gxx + conv2_sep(Ixx.data(), w, h, array_x1, winsize, array_y1, winsize); + + //stim::cpu2image(Ixx.data(), "data_output/Ixx_0915.bmp", w, h, stim::cmBrewer); + // get Iyy by convolving the image with gyy + conv2_sep(Iyy.data(), w, h, array_x2, winsize, array_y2, winsize); + + //stim::cpu2image(Iyy.data(), "data_output/Iyy_0915.bmp", w, h, stim::cmBrewer); + + delete [] array_x1; //free the memory of array_x1 + delete [] array_y1; //free the memory of array_y1 + delete [] array_x2; //free the memory of array_x2 + delete [] array_y2; //free the memory of array_y2 + + I.set_channel(0, Ixx.data()); + I.set_channel(1, Iyy.data()); + + return I; + +} \ No newline at end of file diff --git a/tira/cuda/bsds500/dG_d2x_theta_conv2.cpp b/tira/cuda/bsds500/dG_d2x_theta_conv2.cpp new file mode 100644 index 0000000..a268d95 --- /dev/null +++ b/tira/cuda/bsds500/dG_d2x_theta_conv2.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +/// This function evaluates the theta-dependent even-symmetric gaussian derivative gradient of an one-channel image + +/// @param img is the one-channel image +/// @param r is an array of radii for different scaled discs(filters) +/// @param sigma_n is the number of standard deviations used to define the sigma +/// @param theta is angle used for computing the gradient + +void conv2(float* img, float* mask, float* cpu_copy, unsigned int w, unsigned int h, unsigned int M); +void array_abs(float* img, unsigned int N); + +stim::image Gd_even(stim::image image, int r, unsigned int sigma_n, float theta){ + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + int winsize = 2 * r + 1; // set the winsdow size of disc(filter) + float sigma = float(r)/float(sigma_n); // calculate the sigma used in gaussian function + + stim::image I(w, h, 1, 2); // allocate space for return image class + stim::image Gd_even_theta(w, h); // allocate space for Gd_even_theta + stim::image mask_x(winsize, winsize); // allocate space for x-axis-oriented filter + stim::image mask_r(winsize, winsize); // allocate space for theta-oriented filter + + for (int j = 0; j < winsize; j++){ + for (int i = 0; i< winsize; i++){ + + int x = i - r; //range of x + int y = j - r; //range of y + + // create the x-oriented gaussian derivative filter mask_x + mask_x.data()[j*winsize + i] = (-1) * (1 - pow(x, 2)) * exp((-1)*(pow(x, 2))/(2*pow(sigma, 2))) * exp((-1)*(pow(y, 2))/(2*pow(sigma, 2))); + + } + } + + mask_r = mask_x.rotate(theta, r, r); + //mask_r = mask_x.rotate(45, r, r); + //stim::cpu2image(mask_r.data(), "data_output/mask_r_0919.bmp", winsize, winsize, stim::cmBrewer); + + // do the 2D convolution with image and mask + conv2(image.data(), mask_r.data(), Gd_even_theta.data(), w, h, winsize); + array_abs(Gd_even_theta.data(), N); + + //stim::cpu2image(Gd_even_theta.data(), "data_output/Gd_even_theta_0919.bmp", w, h, stim::cmGrayscale); + + return Gd_even_theta; +} \ No newline at end of file diff --git a/tira/cuda/bsds500/kmeans.cpp b/tira/cuda/bsds500/kmeans.cpp new file mode 100644 index 0000000..fee17b7 --- /dev/null +++ b/tira/cuda/bsds500/kmeans.cpp @@ -0,0 +1,67 @@ +#include +//#include +#include +#include +#include +#include + +/// This function use cvkmeans to cluster given textons + +/// @param testons is a multi-channel image +/// @param k is the number of clusters + +stim::image kmeans(stim::image textons, unsigned int K){ + + unsigned int w = textons.width(); // get the width of picture + unsigned int h = textons.height(); // get the height of picture + unsigned int feature_n = textons.channels(); // get the spectrum of picture + unsigned int N = w * h; // get the number of pixels + + float* sample1 = (float*) malloc(sizeof(float) * N * feature_n); //allocate the space for textons + + //reallocate a multi-channel texton image to a single-channel image + for(unsigned int c = 0; c < feature_n; c++){ + + stim::image temp; + temp = textons.channel(c); + + for(unsigned int j = 0; j < N; j++){ + + sample1[c + j * feature_n] = temp.data()[j]; + } + } + + + cv::Mat sample2(N, feature_n, CV_32F, sample1); //copy image to cv::mat + + //(optional) show the test result + //imshow("sample2", sample2); + + + cv::TermCriteria criteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 10, 0.1); // set stop-criteria for kmeans iteration + cv::Mat labels(N, 1, CV_8U, cvScalarAll(0)); // allocate space for kmeans output + cv::Mat centers; // allocate space for kmeans output + + unsigned int test_times = 2; // set the number of times of trying kmeans, it will return the best result + + cv::kmeans(sample2, K, labels, criteria, test_times, cv::KMEANS_PP_CENTERS, centers); // kmeans clustering + + //(optional) show the test result + //imwrite( "data_output/labels_1D.bmp", labels); + + stim::image texture(w, h, 1, 1); // allocate space for texture + + for(unsigned int i = 0; i < N; i++){ // reshape the labels from iD array to image + + texture.data()[i] = labels.at(i); + + } + + //texture.save("data_output/kmeans_test0924_2.bmp"); + + //(optional) show the test result + //stim::cpu2image(texture.data(), "data_output/kmeans_test.bmp", w, h, stim::cmBrewer); + + return texture; + +} \ No newline at end of file diff --git a/tira/cuda/bsds500/laplacian_conv2.cpp b/tira/cuda/bsds500/laplacian_conv2.cpp new file mode 100644 index 0000000..4b41d0d --- /dev/null +++ b/tira/cuda/bsds500/laplacian_conv2.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#define PI 3.1415926 + +void array_multiply(float* lhs, float rhs, unsigned int N); +void array_add(float* ptr1, float* ptr2, float* sum, unsigned int N); +void array_abs(float* img, unsigned int N); + +/// This function evaluates the center-surround(Laplacian of Gaussian) gaussian derivative gradient of an one-channel image + +/// @param img is the one-channel image +/// @param r is an array of radii for different scaled discs(filters) +/// @param sigma_n is the number of standard deviations used to define the sigma + +stim::image Gd_center(stim::image image, int r, unsigned int sigma_n){ + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + int winsize = 2 * r + 1; // set the winsdow size of disc(filter) + + stim::image I(w, h, 1, 2); // allocate space for return image of Gd2 + stim::image Ixx(w, h); // allocate space for Ixx + stim::image Iyy(w, h); // allocate space for Iyy + stim::image Gd_center(w, h); // allocate space for Pb + + I = Gd2(image, r, sigma_n); // calculate the Ixx, Iyy + Ixx = I.channel(0); + Iyy = I.channel(1); + + array_add(Ixx.data(), Iyy.data(), Gd_center.data(), N); //Gd_center = Ixx + Iyy; + array_abs(Gd_center.data(), N); + + //stim::cpu2image(Gd_center.data(), "data_output/Gd_center_0919.bmp", w, h, stim::cmBrewer); + + return Gd_center; + +} diff --git a/tira/cuda/bsds500/tPb.cpp b/tira/cuda/bsds500/tPb.cpp new file mode 100644 index 0000000..e066dae --- /dev/null +++ b/tira/cuda/bsds500/tPb.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + + +void array_multiply(float* lhs, float rhs, unsigned int N); +void array_add(float* ptr1, float* ptr2, float* sum, unsigned int N); +void chi_grad(float* img, float* cpu_copy, unsigned int w, unsigned int h, int r, unsigned int bin_n, unsigned int bin_size, float theta); + +/// This function evaluates the tPb given a grayscale image + +/// @param img is the multi-channel image +/// @param theta_n is the number of angles used for computing oriented chi-gradient +/// @param r is an array of radii for different scaled discs(filters) +/// @param alpha is is an array of weights for different scaled discs(filters) +/// @param s is the number of scales +/// @param K is the number of clusters + +stim::image tPb(stim::image img, int* r, float* alpha, unsigned int theta_n, unsigned int bin_n, int s, unsigned K){ + + unsigned int w = img.width(); // get the width of picture + unsigned int h = img.height(); // get the height of picture + unsigned int N = w * h; // get the number of pixels + + stim::image img_textons(w, h, 1, theta_n*2+1); // allocate space for img_textons + stim::image img_texture(w, h, 1, 1); // allocate space for img_texture + stim::image tPb_theta(w, h, 1, 1); // allocate space for tPb_theta + stim::image tPb(w, h, 1, 1); // allocate space for tPb + unsigned size = tPb_theta.size(); // get the size of tPb_theta + memset (tPb.data(), 0, size * sizeof(float)); // initialize all the pixels of tPb to 0 + stim::image temp(w, h, 1, 1); // set the temporary image to store the addtion result + + std::ostringstream ss; // (optional) set the stream to designate the test result file + + + img_textons = textons(img, theta_n); + + img_texture = kmeans(img_textons, K); // changing kmeans result into float type is required + + stim::cpu2image(img_texture.data(), "data_output/texture_0925.bmp", w, h, stim::cmBrewer); + + + unsigned int max1 = img_texture.maxv(); // get the maximum of Pb used for normalization + unsigned int bin_size = (max1 + 1)/bin_n; // (whether"+1" or not depends on kmeans result) + + for (int i = 0; i < theta_n; i++){ + + float theta = 180 * ((float)i/theta_n); // calculate the even-splited angle for each tPb_theta + + memset (tPb_theta.data(), 0, size * sizeof(float)); // initialize all the pixels of tPb_theta to 0 + + //ss << "data_output/0922tPb_theta"<< theta << ".bmp"; // set the name for test result file (optional) + //std::string sss = ss.str(); + + for (int j = 0; j < s; j++){ + + // get the chi-gradient by convolving each image slice with the mask + chi_grad(img_texture.data(), temp.data(), w, h, r[j], bin_n, bin_size, theta); + + float max2 = temp.maxv(); // get the maximum of tPb_theta used for normalization + array_multiply(temp.data(), 1/max2, N); // normalize the tPb_theta + + //output the test result of each slice (optional) + //stim::cpu2image(temp.data(), "data_output/tPb_slice0924_2.bmp", w, h, stim::cmBrewer); + + // multiply each chi-gradient with its weight + array_multiply(temp.data(), alpha[j], N); + + // add up all the weighted chi-gradients + array_add(tPb_theta.data(), temp.data(), tPb_theta.data(), N); + + + } + + //ss.str(""); //(optional) clear the space for stream + + for(unsigned long ti = 0; ti < N; ti++){ + + if(tPb_theta.data()[ti] > tPb.data()[ti]){ //get the maximum value among all tPb_theta for ith pixel + tPb.data()[ti] = tPb_theta.data()[ti]; + } + + else{ + } + } + } + + float max3 = tPb.maxv(); // get the maximum of tPb used for normalization + array_multiply(tPb.data(), 1/max3, N); // normalize the tPb + + //output the test result of tPb (optional) + //stim::cpu2image(tPb.data(), "data_output/tPb_0922.bmp", w, h, stim::cmBrewer); + + return tPb; +} diff --git a/tira/cuda/bsds500/textons.cpp b/tira/cuda/bsds500/textons.cpp new file mode 100644 index 0000000..dbe66f1 --- /dev/null +++ b/tira/cuda/bsds500/textons.cpp @@ -0,0 +1,61 @@ +#include +//#include +#include +#include +#include + +/// This function convolve the grayscale image with a set of oriented Gaussian +/// derivative filters, and return a texton image with (theta_n*2+1) channels + +/// @param image is an one-channel grayscale image +/// @param theta_n is the number of angles used for computing the gradient + +stim::image textons(stim::image image, unsigned int theta_n){ + + unsigned int w = image.width(); // get the width of picture + unsigned int h = image.height(); // get the height of picture + unsigned N = w * h; // get the number of pixels of picture + + stim::image textons(w, h, 1, theta_n*2+1); // allocate space for textons + stim::image temp(w, h); // allocate space for temp + + unsigned int r_odd = 3; // set disc radii for odd-symmetric filter + unsigned int sigma_n_odd = 3; // set sigma_n for odd-symmetric filter + unsigned int r_even = 3; // set disc radii for even-symmetric filter + unsigned int sigma_n_even = 3; // set sigma_n for even-symmetric filter + unsigned int r_center = 3; // set disc radii for center-surround filter + unsigned int sigma_n_center = 3; // set sigma_n for center-surround filter + + //std::ostringstream ss1, ss2; // (optional) set the stream to designate the test result file + + for (unsigned int i = 0; i < theta_n; i++){ + + //ss1 << "data_output/textons_channel_"<< i << ".bmp"; // set the name for test result file (optional) + //std::string sss1 = ss1.str(); + //ss2 << "data_output/textons_channel_"<< i+theta_n << ".bmp"; // set the name for test result file (optional) + //std::string sss2 = ss2.str(); + + float theta = 180 * ((float)i/theta_n); // calculate the even-splited angle for each oriented filter + + temp = Gd_odd(image, r_odd, sigma_n_odd, theta); // return Gd_odd to temp + //stim::cpu2image(temp.data(), sss1, w, h, stim::cmBrewer); + textons.set_channel(i, temp.data()); // copy temp to ith channel of textons + + temp = Gd_even(image, r_even, sigma_n_even, theta); // return Gd_even to temp + //stim::cpu2image(temp.data(), sss2, w, h, stim::cmBrewer); + textons.set_channel(i + theta_n, temp.data()); // copy temp to (i+theta_n)th channel of textons + + //ss1.str(""); //(optional) clear the space for stream + //ss2.str(""); //(optional) clear the space for stream + + } + + temp = Gd_center(image, r_center, sigma_n_center); // return Gd_center to temp + //stim::cpu2image(temp.data(), "data_output/textons_channel_16.bmp", w, h, stim::cmBrewer); + textons.set_channel(theta_n*2, temp.data()); // copy temp to (theta_n*2)th channel of textons + + return textons; + +} + + \ No newline at end of file diff --git a/tira/cuda/cost.h b/tira/cuda/cost.h new file mode 100644 index 0000000..aaed177 --- /dev/null +++ b/tira/cuda/cost.h @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +///Cost function that works with the gl-spider class to find index of the item with min-cost. +typedef unsigned char uchar; +texture texIn; +float *result; +cudaArray* srcArray; +bool testing = false; + +inline void checkCUDAerrors(const char *msg) +{ + cudaError_t err = cudaGetLastError(); + if (cudaSuccess != err){ + fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString(err) ); + exit(1); + } +} + +///A virtual representation of a uniform template. +///Returns the value of the template pixel. +///@param x, location of a pixel. +__device__ float Template(int x) +{ + if(x < 16/6 || x > 16*5/6 || (x > 16*2/6 && x < 16*4/6)){ + return 1.0; + }else{ + return 0.0; + } + +} + +///Find the difference of the given set of samples and the template +///using cuda acceleration. +///@param *result, a pointer to the memory that stores the result. +__global__ +void get_diff (float *result) +{ + //float* shared = SharedMemory(); + __shared__ float shared[16][8]; + int x = threadIdx.x + blockIdx.x * blockDim.x; + int y = threadIdx.y + blockIdx.y * blockDim.y; + int x_t = threadIdx.x; + int y_t = threadIdx.y; + //int idx = y*16+x; + int g_idx = blockIdx.y; + + float valIn = tex2D(texIn, x, y)/255.0; + float valTemp = Template(x); + shared[x_t][y_t] = abs(valIn-valTemp); + + __syncthreads(); + + for(unsigned int step = blockDim.x/2; step >= 1; step >>= 1) + { + __syncthreads(); + if (x_t < step) + { + shared[x_t][y_t] += shared[x_t + step][y_t]; + } + __syncthreads(); + } + __syncthreads(); + + for(unsigned int step = blockDim.y/2; step >= 1; step >>= 1) + { + __syncthreads(); + if(y_t < step) + { + shared[x_t][y_t] += shared[x_t][y_t + step]; + } + __syncthreads(); + } + __syncthreads(); + if(x_t == 0 && y_t == 0) + result[g_idx] = shared[0][0]; + + +// //result[idx] = abs(valIn); +} + + + +///Initialization function, allocates the memory and passes the necessary +///handles from OpenGL and Cuda. +///@param src, cudaGraphicsResource that handles the shared OpenGL/Cuda Texture +///@param DIM_Y, integer controlling how much memory to allocate. +void initArray(cudaGraphicsResource_t src, int DIM_Y) +{ + HANDLE_ERROR( + cudaGraphicsMapResources(1, &src) + ); + HANDLE_ERROR( + cudaGraphicsSubResourceGetMappedArray(&srcArray, src, 0, 0) + ); + HANDLE_ERROR( + cudaBindTextureToArray(texIn, srcArray) + ); + cudaMalloc( (void**) &result, DIM_Y*sizeof(float)); + checkCUDAerrors("Memory Allocation Issue 1"); + //HANDLE_ERROR( + // cudaBindTextureToArray(texIn, ptr, &channelDesc) + // ); +} +///Deinit function that frees the memery used and releases the texture resource +///back to OpenGL. +///@param src, cudaGraphicsResource that handles the shared OpenGL/Cuda Texture +void cleanUP(cudaGraphicsResource_t src) +{ + HANDLE_ERROR( + cudaFree(result) + ); + HANDLE_ERROR( + cudaGraphicsUnmapResources(1,&src) + ); + HANDLE_ERROR( + cudaUnbindTexture(texIn) + ); +} + + + +///External access-point to the cuda function +///@param src, cudaGraphicsResource that handles the shared OpenGL/Cuda Texture +///@param DIM_Y, the number of samples in the template. +///@inter temporary paramenter that tracks the number of times cost.h was called. +extern "C" +stim::vec get_cost(cudaGraphicsResource_t src, int DIM_Y) +{ +// int minGridSize; +// int blockSize; + +// cudaOccupancyMaxPotentialBlockSize(&minGridSize, &blockSize, get_diff, 0, 20*DIM_Y*10); +// std::cout << blockSize << std::endl; +// std::cout << minGridSize << std::endl; +// stringstream name; //for debugging +// name << "Test.bmp"; +// dim3 block(4,4); +// dim3 grid(20/4, DIM_Y*10/4); +// int gridSize = (DIM_Y*10*20 + 1024 - 1)/1024; +// dim3 grid(26, 26); +// dim3 grid = GenGrid1D(DIM_Y*10*20); +// stim::gpu2image(result, name.str(), 20,DIM_Y*10,0,1); +// name.clear(); +// name << "sample_" << inter << "_" << idx << ".bmp"; +// stim::gpu2image(v_dif, name.str(), 20,10,0,1); + + //float output[DIM_Y]; + float *output; + output = (float* ) malloc(DIM_Y*sizeof(float)); + stim::vec ret(0, 0); + float mini = 10000000000000000.0; + int idx = 0; + initArray(src, DIM_Y*8); + dim3 numBlocks(1, DIM_Y); + dim3 threadsPerBlock(16, 8); + + + get_diff <<< numBlocks, threadsPerBlock >>> (result); + cudaMemcpy(output, result, DIM_Y*sizeof(float), cudaMemcpyDeviceToHost); + + for( int i = 0; i +__global__ void cuda_crop2d(T* dest, T* src, size_t sx, size_t sy, size_t x, size_t y, size_t dx, size_t dy){ + + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; //calculate the current working position within the destination image + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + if(xi >= dx || yi >= dy) return; //if this thread is outside of the destination image, return + + size_t di = yi * dx + xi; //calculate the 1D index into the destination image + size_t si = (y + yi) * sx + (x + xi); //calculate the 1D index into the source image + + dest[di] = src[si]; //copy the corresponding source pixel to the destination image +} + +/// Crops a 2D image composed of elements of type T +/// @param dest is a device pointer to memory of size dx*dy that will store the cropped image +/// @param src is a device pointer to memory of size sx*sy that stores the original image +/// @param sx is the size of the source image along x +/// @param sy is the size of the source image along y +/// @param x is the x-coordinate of the start position of the cropped region within the source image +/// @param y is the y-coordinate of the start position of the cropped region within the source image +/// @param dx is the size of the destination image along x +/// @param dy is the size of the destination image along y +template +void gpu_crop2d(T* dest, T* src, size_t sx, size_t sy, size_t x, size_t y, size_t dx, size_t dy){ + int max_threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 threads( sqrt(max_threads), sqrt(max_threads) ); + dim3 blocks( dx / sqrt(threads.x) + 1, dy / sqrt(threads.y) + 1); //calculate the optimal number of blocks + cuda_crop2d <<< blocks, threads >>>(dest, src, sx, sy, x, y, dx, dy); +} \ No newline at end of file diff --git a/tira/cuda/cuda_texture.cuh b/tira/cuda/cuda_texture.cuh new file mode 100644 index 0000000..3b5460e --- /dev/null +++ b/tira/cuda/cuda_texture.cuh @@ -0,0 +1,185 @@ +#ifndef STIM_CUDA_TEXTURE_H +#define STIM_CUDA_TEXTURE_H + +#include +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include + +///A container for the texture based methods used by the spider class. +namespace stim +{ + namespace cuda + { + class cuda_texture + { + public: + cudaArray* srcArray; + cudaGraphicsResource_t resource; + struct cudaResourceDesc resDesc; + struct cudaTextureDesc texDesc; + cudaTextureObject_t tObj; + float *result; + + + ///basic constructor that creates the texture with default parameters. + cuda_texture() + { + memset(&texDesc, 0, sizeof(texDesc)); + texDesc.addressMode[0] = cudaAddressModeWrap; + texDesc.addressMode[1] = cudaAddressModeWrap; + texDesc.filterMode = cudaFilterModePoint; + texDesc.readMode = cudaReadModeElementType; + texDesc.normalizedCoords = 0; + } + + ///basic destructor + ~cuda_texture() + { + UnmapCudaTexture(); + if(result != NULL) + cudaFree(result); + } + + + ///Enable the nromalized texture coordinates. + ///@param bool, 1 for on, 0 for off + void + SetTextureCoordinates(bool val) + { + if(val) + texDesc.normalizedCoords = 1; + else + texDesc.normalizedCoords = 0; + } + + ///sets the dimension dim to used the mode at the borders of the texture. + ///@param dim : 0-x, 1-y, 2-z + ///@param mode: cudaAddressModeWrap = 0, + /// cudaAddressModeClamp = 1, + /// cudaAddressMNodeMirror = 2, + /// cudaAddressModeBorder = 3, + void + SetAddressMode(int dim, int mode) + { + switch(mode) + { + case 0: + texDesc.addressMode[dim] = cudaAddressModeWrap; + break; + case 1: + texDesc.addressMode[dim] = cudaAddressModeClamp; + break; + case 2: + texDesc.addressMode[dim] = cudaAddressModeMirror; + break; + case 3: + texDesc.addressMode[dim] = cudaAddressModeBorder; + break; + default: + break; + } + } + +//-------------------------------------------------------------------------// +//-------------------------------CUDA_MAPPING------------------------------// +//-------------------------------------------------------------------------// +//Methods for creating the cuda texture. + ///@param GLuint tex -- GLtexture (must be contained in a frame buffer object) + /// that holds that data that will be handed to cuda. + ///@param GLenum target -- either GL_TEXTURE_1D, GL_TEXTURE_2D or GL_TEXTURE_3D + /// map work with other gl texture types but untested. + ///Maps the gl texture in cuda memory, binds that data to a cuda array, and binds the cuda + ///array to a cuda texture. + void + MapCudaTexture(GLuint tex, GLenum target) + { + HANDLE_ERROR( + cudaGraphicsGLRegisterImage( + &resource, + tex, + target, + cudaGraphicsMapFlagsReadOnly +// cudaGraphicsRegisterFlagsNone + ) + ); + + HANDLE_ERROR( + cudaGraphicsMapResources(1, &resource) + ); + + HANDLE_ERROR( + cudaGraphicsSubResourceGetMappedArray(&srcArray, resource, 0, 0) + ); + + memset(&resDesc, 0, sizeof(resDesc)); + resDesc.resType = cudaResourceTypeArray; + resDesc.res.array.array = srcArray; + HANDLE_ERROR( + cudaCreateTextureObject(&tObj, &resDesc, &texDesc, NULL) + ); + } + + ///Unmaps the gl texture, binds that data to a cuda array, and binds the cuda + ///array to a cuda texture. + void + UnmapCudaTexture() + { + // HANDLE_ERROR( + // cudaGraphicsUnmapResources(1, &resource) + // ); + // HANDLE_ERROR( + // cudaGraphicsUnregisterResource(resource) + // ); + // HANDLE_ERROR( + // cudaDestroyTextureObject(tObj) + // ); +// HANDLE_ERROR( +// cudaFreeArray(srcArray) +// ); + } + + ///Allocate the auxiliary internal 1D float array + void + Alloc(int x) + { + cudaMalloc( (void**) &result, x*sizeof(float)); + } + +//-------------------------------------------------------------------------// +//------------------------------GET/SET METHODS----------------------------// +//-------------------------------------------------------------------------// + +///Returns the bound texture object. + cudaTextureObject_t + getTexture() + { + return tObj; + } + + cudaArray* + getArray() + { + return srcArray; + } + + float* + getAuxArray() + { + return result; + } + }; +} +} + + +#endif diff --git a/tira/cuda/cudatools.h b/tira/cuda/cudatools.h new file mode 100644 index 0000000..1f1a9e6 --- /dev/null +++ b/tira/cuda/cudatools.h @@ -0,0 +1,5 @@ +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/tira/cuda/cudatools/callable.h b/tira/cuda/cudatools/callable.h new file mode 100644 index 0000000..601d852 --- /dev/null +++ b/tira/cuda/cudatools/callable.h @@ -0,0 +1,16 @@ +#ifndef CUDA_CALLABLE + +//define the CUDA_CALLABLE macro (will prefix all members) +#ifdef __CUDACC__ +#define CUDA_CALLABLE __host__ __device__ inline +#else +#define CUDA_CALLABLE +#endif + +#ifdef __CUDACC__ +#define CUDA_UNCALLABLE __host__ inline +#else +#define CUDA_UNCALLABLE +#endif + +#endif diff --git a/tira/cuda/cudatools/devices.h b/tira/cuda/cudatools/devices.h new file mode 100644 index 0000000..9d56f78 --- /dev/null +++ b/tira/cuda/cudatools/devices.h @@ -0,0 +1,80 @@ +#ifndef STIM_CUDA_DEVICES +#define STIM_CUDA_DEVICES + +#include + +namespace stim{ + extern "C" + int maxThreadsPerBlock(){ + int device; + cudaGetDevice(&device); //get the id of the current device + cudaDeviceProp props; //device property structure + cudaGetDeviceProperties(&props, device); + return props.maxThreadsPerBlock; + } + + extern "C" + size_t sharedMemPerBlock(){ + int device; + cudaGetDevice(&device); //get the id of the current device + cudaDeviceProp props; //device property structure + cudaGetDeviceProperties(&props, device); + return props.sharedMemPerBlock; + } + + extern "C" + size_t constMem(){ + int device; + cudaGetDevice(&device); //get the id of the current device + cudaDeviceProp props; //device property structure + cudaGetDeviceProperties(&props, device); + return props.totalConstMem; + } + + //tests that a given device ID is valid and provides at least the specified compute capability + bool testDevice(int d, int major, int minor){ + int nd; + cudaGetDeviceCount(&nd); //get the number of CUDA devices + if(d <= nd && d >= 0) { //if the given ID has an associated device + cudaDeviceProp props; + cudaGetDeviceProperties(&props, d); //get the device properties structure + if(props.major > major){ + return true; + } + else if(props.major == major && props.minor >= minor){ + return true; + } + } + return false; + } + + //tests each device ID in a list and returns the number of devices that fit the desired + // compute capability + int testDevices(int* dlist, unsigned n_devices, int major, int minor){ + int valid = 0; + for(unsigned d = 0; d < n_devices; d++){ + if(testDevice(dlist[d], major, minor)) + valid++; + } + return valid; + } + + void printDevice(int device){ + int nd; + cudaGetDeviceCount(&nd); //get the number of CUDA devices + printf("CUDA Device Diagnosis: [%i]\n", device); + if(device < 0){ + printf("Device %i is an invalid device ID\n", device); + } + else if(device >= nd){ + printf("Device %i is unavailable - only %i devices are detected", device, nd); + } + else{ + cudaDeviceProp props; + cudaGetDeviceProperties(&props, device); //get the device properties structure + printf("compute capability: %i.%i\n", props.major, props.minor); + } + } +} //end namespace rts + +#endif diff --git a/tira/cuda/cudatools/error.h b/tira/cuda/cudatools/error.h new file mode 100644 index 0000000..46cf2a9 --- /dev/null +++ b/tira/cuda/cudatools/error.h @@ -0,0 +1,65 @@ +#ifndef STIM_CUDA_ERROR_H +#define STIM_CUDA_ERROR_H + +#include +#include +#include "cuda_runtime.h" +#include "device_launch_parameters.h" +#include "cufft.h" +#include "cublas_v2.h" + +//handle error macro +static void cuHandleError( cudaError_t err, const char *file, int line ) { + if (err != cudaSuccess) { + printf("%s in %s at line %d\n", cudaGetErrorString( err ), file, line ); + + } +} +#define HANDLE_ERROR( err ) (cuHandleError( err, __FILE__, __LINE__ )) +static void cufftHandleError( cufftResult err, const char*file, int line ) +{ + if (err != CUFFT_SUCCESS) + { + if(err == CUFFT_INVALID_PLAN) + std::cout<<"The plan parameter is not a valid handle."< +#ifdef WIN32 + #include + #include +#else + #include + #include + +#endif + + + +#include +#include + +//#include +#include "cuda_gl_interop.h" +#include + +namespace stim +{ + +static void InitGLEW() +{ + //Initialize the GLEW toolkit + + /*GLenum err = glewInit(); + if(GLEW_OK != err) + { + printf("Error starting GLEW."); + } + fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));*/ +} + +static void cudaSetDevice(int major = 1, int minor = 3) +{ + cudaDeviceProp prop; + int dev; + + //find a CUDA device that can handle an offscreen buffer + int num_gpu; + HANDLE_ERROR(cudaGetDeviceCount(&num_gpu)); + printf("Number of CUDA devices detected: %d\n", num_gpu); + memset(&prop, 0, sizeof(cudaDeviceProp)); + prop.major=major; + prop.minor=minor; + HANDLE_ERROR(cudaChooseDevice(&dev, &prop)); + HANDLE_ERROR(cudaGetDeviceProperties(&prop, dev)); + HANDLE_ERROR(cudaGLSetGLDevice(dev)); +} + +static void* cudaMapResource(cudaGraphicsResource* cudaBufferResource) +{ + //this function takes a predefined CUDA resource and maps it to a pointer + void* buffer; + HANDLE_ERROR(cudaGraphicsMapResources(1, &cudaBufferResource, NULL)); + size_t size; + HANDLE_ERROR(cudaGraphicsResourceGetMappedPointer( (void**)&buffer, &size, cudaBufferResource)); + return buffer; +} +static void cudaUnmapResource(cudaGraphicsResource* resource) +{ + //this function unmaps the CUDA resource so it can be used by OpenGL + HANDLE_ERROR(cudaGraphicsUnmapResources(1, &resource, NULL)); +} + +static void cudaCreateRenderBuffer(GLuint &glBufferName, cudaGraphicsResource* &cudaBufferResource, int resX, int resY) +{ + //delete the previous buffer name and resource + if(cudaBufferResource != 0) + HANDLE_ERROR(cudaGraphicsUnregisterResource(cudaBufferResource)); + if(glBufferName != 0) + glDeleteBuffers(1, &glBufferName); + + //generate an OpenGL offscreen buffer + glGenBuffers(1, &glBufferName); + + //bind the buffer - directs all calls to this buffer + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, glBufferName); + glBufferData(GL_PIXEL_UNPACK_BUFFER, resX * resY * sizeof(uchar3), NULL, GL_DYNAMIC_DRAW_ARB); + CHECK_OPENGL_ERROR + HANDLE_ERROR(cudaGraphicsGLRegisterBuffer(&cudaBufferResource, glBufferName, cudaGraphicsMapFlagsNone)); +} +} +#endif diff --git a/tira/cuda/cudatools/threads.h b/tira/cuda/cudatools/threads.h new file mode 100644 index 0000000..0315082 --- /dev/null +++ b/tira/cuda/cudatools/threads.h @@ -0,0 +1,34 @@ +#include "cuda_runtime.h" +#include "device_launch_parameters.h" +#include + +#ifndef CUDA_THREADS_H +#define CUDA_THREADS_H + +#define MAX_GRID 65535 + +__device__ unsigned int ThreadIndex1D() +{ + return blockIdx.y * gridDim.x * blockDim.x + blockIdx.x * blockDim.x + threadIdx.x; +} + +dim3 GenGrid1D(unsigned int N, unsigned int blocksize = 128) +{ + dim3 dimgrid; + + dimgrid.x = (N + blocksize - 1)/blocksize; + dimgrid.y = 1; + dimgrid.z = 1; + + if(dimgrid.x > MAX_GRID) + { + dimgrid.y = (dimgrid.x + MAX_GRID - 1) / MAX_GRID; + dimgrid.x = MAX_GRID; + } + + return dimgrid; + +} + + +#endif diff --git a/tira/cuda/cudatools/timer.h b/tira/cuda/cudatools/timer.h new file mode 100644 index 0000000..27d6352 --- /dev/null +++ b/tira/cuda/cudatools/timer.h @@ -0,0 +1,31 @@ +#ifndef STIM_CUDA_TIMER +#define STIM_CUDA_TIMER + +static cudaEvent_t tStartEvent; +static cudaEvent_t tStopEvent; + +namespace stim{ + +/// These functions calculate the time between GPU functions in milliseconds +static void gpuStartTimer() +{ + //set up timing events + cudaEventCreate(&tStartEvent); + cudaEventCreate(&tStopEvent); + cudaEventRecord(tStartEvent, 0); +} + +static float gpuStopTimer() +{ + cudaEventRecord(tStopEvent, 0); + cudaEventSynchronize(tStopEvent); + float elapsedTime; + cudaEventElapsedTime(&elapsedTime, tStartEvent, tStopEvent); + cudaEventDestroy(tStartEvent); + cudaEventDestroy(tStopEvent); + return elapsedTime; +} + +} //end namespace stim + +#endif \ No newline at end of file diff --git a/tira/cuda/filter.cuh b/tira/cuda/filter.cuh new file mode 100644 index 0000000..3e21ee0 --- /dev/null +++ b/tira/cuda/filter.cuh @@ -0,0 +1,197 @@ +#ifndef STIM_FILTER_H +#define STIM_FILTER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IMAD(a,b,c) ( __mul24((a), (b)) + (c) ) + + +namespace stim +{ + namespace cuda + { + + float* gpuLoG; + float* LoG; + float* res; + float* centers; + +//#ifdef DEBUG + float* print; +//#endif + + stim::cuda::cuda_texture tx; + + + + void initArray(int DIM_X, int DIM_Y, int kl) + { + + LoG = (float*) malloc(kl*kl*sizeof(float)); + HANDLE_ERROR( + cudaMalloc( (void**) &gpuLoG, kl*kl*sizeof(float)) + ); + // checkCUDAerrors("Memory Allocation, LoG"); + HANDLE_ERROR( + cudaMalloc( (void**) &res, DIM_Y*DIM_X*sizeof(float)) + ); + HANDLE_ERROR( + cudaMalloc( (void**) ¢ers, DIM_Y*DIM_X*sizeof(float)) + ); + +//#ifdef DEBUG + HANDLE_ERROR( + cudaMalloc( (void**) &print, DIM_Y*DIM_X*sizeof(float)) + ); +//#endif + + // checkCUDAerrors("Memory Allocation, Result"); + } + + void cleanUP() + { + HANDLE_ERROR( + cudaFree(gpuLoG) + ); + HANDLE_ERROR( + cudaFree(res) + ); + HANDLE_ERROR( + cudaFree(centers) + ); +//#ifdef DEBUG + HANDLE_ERROR( + cudaFree(print) + ); +//#endif + free(LoG); + } + + void + filterKernel (float kl, float sigma, float *LoG) + { + float t = 0.0; + float kr = kl/2; + float x, y; + int idx; + for(int i = 0; i < kl; i++){ + for(int j = 0; j < kl; j++){ + idx = j*kl+i; + x = i - kr - 0.5; + y = j - kr - 0.5; + LoG[idx] = (-1.0/PI/powf(sigma, 4))* (1 - (powf(x,2)+powf(y,2))/2.0/powf(sigma, 2)) + *expf(-(powf(x,2)+powf(y,2))/2/powf(sigma,2)); + t +=LoG[idx]; + } + } + + for(int i = 0; i < kl*kl; i++) + { + LoG[i] = LoG[i]/t; + } + + } + + //Shared memory would be better. + __global__ + void + applyFilter(cudaTextureObject_t texIn, unsigned int DIM_X, unsigned int DIM_Y, int kr, int kl, float *res, float* gpuLoG, float* p){ + //R = floor(size/2) + //THIS IS A NAIVE WAY TO DO IT, and there is a better way) + + __shared__ float shared[7][7]; + int x = blockIdx.x; + int y = blockIdx.y; + int xi = threadIdx.x; + int yi = threadIdx.y; + // float val = 0; + float tu = (x-kr+xi)/(float)DIM_X; + float tv = (y-kr+yi)/(float)DIM_Y; + int idx = y*DIM_X+x; + shared[xi][yi] = gpuLoG[yi*kl+xi]*(255.0-(float)tex2D(texIn, tu, tv)); + __syncthreads(); + + //x = max(0,x); + //x = min(x, width-1); + //y = max(y, 0); + //y = min(y, height - 1); + + // int k_idx; + for(unsigned int step = blockDim.x/2; step >= 1; step >>= 1) + { + __syncthreads(); + if (xi < step) + { + shared[xi][yi] += shared[xi + step][yi]; + } + __syncthreads(); + } + __syncthreads(); + + for(unsigned int step = blockDim.y/2; step >= 1; step >>= 1) + { + __syncthreads(); + if(yi < step) + { + shared[xi][yi] += shared[xi][yi + step]; + } + __syncthreads(); + } + __syncthreads(); + if(xi == 0 && yi == 0) + res[idx] = shared[0][0]; + + } + + extern "C" + float * + get_centers(GLint texbufferID, GLenum texType, int DIM_X, int DIM_Y, int sizeK, float sigma, float conn, int iter = 0) + { + tx.SetTextureCoordinates(1); + tx.SetAddressMode(1, 3); + tx.MapCudaTexture(texbufferID, texType); + float* result = (float*) malloc(DIM_X*DIM_Y*sizeof(float)); + + initArray(DIM_X, DIM_Y, sizeK); + + filterKernel(sizeK, sigma, LoG); + cudaMemcpy(gpuLoG, LoG, sizeK*sizeK*sizeof(float), cudaMemcpyHostToDevice); + dim3 numBlocks(DIM_X, DIM_Y); + dim3 threadsPerBlock(sizeK, sizeK); + + applyFilter <<< numBlocks, threadsPerBlock >>> (tx.getTexture(), DIM_X, DIM_Y, floor(sizeK/2), sizeK, res, gpuLoG, print); + + #ifdef DEBUG + stringstream name; + name.str(""); + name << "Fiber Cylinder " << iter << ".bmp"; + stim::gpu2image(res, name.str(), DIM_X, DIM_Y, 0, 255); + #endif + + + stim::cuda::gpu_local_max(centers, res, conn, DIM_X, DIM_Y); + + cudaDeviceSynchronize(); + + + cudaMemcpy(result, centers, DIM_X*DIM_Y*sizeof(float), cudaMemcpyDeviceToHost); + + tx.UnmapCudaTexture(); + cleanUP(); + return result; + } + + } +} +#endif diff --git a/tira/cuda/kmeans.cuh b/tira/cuda/kmeans.cuh new file mode 100644 index 0000000..3263bc3 --- /dev/null +++ b/tira/cuda/kmeans.cuh @@ -0,0 +1,366 @@ +//This software is dervied from Professor Wei-keng Liao's parallel k-means +//clustering code obtained on November 21, 2010 from +// http://users.eecs.northwestern.edu/~wkliao/Kmeans/index.html +//(http://users.eecs.northwestern.edu/~wkliao/Kmeans/simple_kmeans.tar.gz). +// +//With his permission, Serban Giuroiu is publishing his CUDA implementation based on his code +//under the open-source MIT license. See the LICENSE file for more details. + +// The original code can be found on Github ( https://github.com/serban/kmeans ) +// Here I have just made a few changes to get it to work + + + + +#define malloc2D(name, xDim, yDim, type) do { \ + name = (type **)malloc(xDim * sizeof(type *)); \ + assert(name != NULL); \ + name[0] = (type *)malloc(xDim * yDim * sizeof(type)); \ + assert(name[0] != NULL); \ + for (size_t i = 1; i < xDim; i++) \ + name[i] = name[i-1] + yDim; \ +} while (0) + + + +static void handleError(cudaError_t error, const char* file, int line){ + + if(error != cudaSuccess){ + cout << cudaGetErrorString(error) << " in " << file << " at line " << line << endl; + exit(1); + } +} + +#define handle_error(error) handleError(error, __FILE__ , __LINE__) + + + +static inline int nextPowerOfTwo(int n) { + n--; + + n = n >> 1 | n; + n = n >> 2 | n; + n = n >> 4 | n; + n = n >> 8 | n; + n = n >> 16 | n; + // n = n >> 32 | n; // For 64-bit ints + + return ++n; +} + +/*----< euclid_dist_2() >----------------------------------------------------*/ +/* square of Euclid distance between two multi-dimensional points */ +__host__ __device__ inline static +float euclid_dist_2(int numCoords, + int numObjs, + int numClusters, + float *objects, // [numCoords][numObjs] + float *clusters, // [numCoords][numClusters] + int objectId, + int clusterId) +{ + int i; + float ans=0.0; + + for (i = 0; i < numCoords; i++) { + ans += (objects[numObjs * i + objectId] - clusters[numClusters * i + clusterId]) * + (objects[numObjs * i + objectId] - clusters[numClusters * i + clusterId]); + } + + return(ans); +} + +/*----< find_nearest_cluster() >---------------------------------------------*/ +__global__ static +void find_nearest_cluster(int numCoords, + int numObjs, + int numClusters, + float *objects, // [numCoords][numObjs] + float *deviceClusters, // [numCoords][numClusters] + int *membership, // [numObjs] + int *intermediates) +{ + extern __shared__ char sharedMemory[]; + + // The type chosen for membershipChanged must be large enough to support + // reductions! There are blockDim.x elements, one for each thread in the + // block. See numThreadsPerClusterBlock in cuda_kmeans(). + unsigned char *membershipChanged = (unsigned char *)sharedMemory; +#ifdef BLOCK_SHARED_MEM_OPTIMIZATION + float *clusters = (float *)(sharedMemory + blockDim.x); +#else + float *clusters = deviceClusters; +#endif + + membershipChanged[threadIdx.x] = 0; + +#ifdef BLOCK_SHARED_MEM_OPTIMIZATION + // BEWARE: We can overrun our shared memory here if there are too many + // clusters or too many coordinates! For reference, a Tesla C1060 has 16 + // KiB of shared memory per block, and a GeForce GTX 480 has 48 KiB of + // shared memory per block. + for (int i = threadIdx.x; i < numClusters; i += blockDim.x) { + for (int j = 0; j < numCoords; j++) { + clusters[numClusters * j + i] = deviceClusters[numClusters * j + i]; + } + } + __syncthreads(); +#endif + + int objectId = blockDim.x * blockIdx.x + threadIdx.x; + + if (objectId < numObjs) { + int index, i; + float dist, min_dist; + + /* find the cluster id that has min distance to object */ + index = 0; + min_dist = euclid_dist_2(numCoords, numObjs, numClusters, + objects, clusters, objectId, 0); + + for (i=1; i 0; s >>= 1) { + if (threadIdx.x < s) { + membershipChanged[threadIdx.x] += + membershipChanged[threadIdx.x + s]; + } + __syncthreads(); + } + + if (threadIdx.x == 0) { + intermediates[blockIdx.x] = membershipChanged[0]; + } + } +} + +__global__ static +void compute_delta(int *deviceIntermediates, + int numIntermediates, // The actual number of intermediates + int numIntermediates2) // The next power of two +{ + // The number of elements in this array should be equal to + // numIntermediates2, the number of threads launched. It *must* be a power + // of two! + extern __shared__ unsigned int intermediates[]; + + // Copy global intermediate values into shared memory. + intermediates[threadIdx.x] = + (threadIdx.x < numIntermediates) ? deviceIntermediates[threadIdx.x] : 0; + + __syncthreads(); + + // numIntermediates2 *must* be a power of two! + for (unsigned int s = numIntermediates2 / 2; s > 0; s >>= 1) { + if (threadIdx.x < s) { + intermediates[threadIdx.x] += intermediates[threadIdx.x + s]; + } + __syncthreads(); + } + + if (threadIdx.x == 0) { + deviceIntermediates[0] = intermediates[0]; + } +} + +/*----< cuda_kmeans() >-------------------------------------------------------*/ +// +// ---------------------------------------- +// DATA LAYOUT +// +// objects [numObjs][numCoords] +// clusters [numClusters][numCoords] +// dimObjects [numCoords][numObjs] +// dimClusters [numCoords][numClusters] +// newClusters [numCoords][numClusters] +// deviceObjects [numCoords][numObjs] +// deviceClusters [numCoords][numClusters] +// ---------------------------------------- +// +/* return an array of cluster centers of size [numClusters][numCoords] */ +float** cuda_kmeans(float **objects, /* in: [numObjs][numCoords] */ + unsigned int numCoords, /* no. features */ + unsigned int numObjs, /* no. objects */ + unsigned int numClusters, /* no. clusters */ + float threshold, /* % objects change membership */ + int *membership, /* out: [numObjs] */ + int loops) +{ + int i, j, index, loop=0; + int *newClusterSize; /* [numClusters]: no. objects assigned in each + new cluster */ + float delta; /* % of objects change their clusters */ + float **dimObjects; + float **clusters; /* out: [numClusters][numCoords] */ + float **dimClusters; + float **newClusters; /* [numCoords][numClusters] */ + + float *deviceObjects; + float *deviceClusters; + int *deviceMembership; + int *deviceIntermediates; + + // Copy objects given in [numObjs][numCoords] layout to new + // [numCoords][numObjs] layout + malloc2D(dimObjects, numCoords, numObjs, float); + for (i = 0; i < numCoords; i++) { + for (j = 0; j < numObjs; j++) { + dimObjects[i][j] = objects[j][i]; + } + } + + /* pick first numClusters elements of objects[] as initial cluster centers*/ + malloc2D(dimClusters, numCoords, numClusters, float); + for (i = 0; i < numCoords; i++) { + for (j = 0; j < numClusters; j++) { + dimClusters[i][j] = dimObjects[i][j]; + } + } + + /* initialize membership[] */ + for (i=0; i deviceProp.sharedMemPerBlock) { + std::cout << "ERROR: insufficient shared memory. Please don't use the definition 'BLOCK_SHARED_MEM_OPTIMIZATION'" << endl; + exit(1); + } +#else + const unsigned int clusterBlockSharedDataSize = + numThreadsPerClusterBlock * sizeof(unsigned char); +#endif + + const unsigned int numReductionThreads = + nextPowerOfTwo(numClusterBlocks); + const unsigned int reductionBlockSharedDataSize = + numReductionThreads * sizeof(unsigned int); + + handle_error(cudaMalloc((void**)&deviceObjects, numObjs*numCoords*sizeof(float))); + handle_error(cudaMalloc((void**)&deviceClusters, numClusters*numCoords*sizeof(float))); + handle_error(cudaMalloc((void**)&deviceMembership, numObjs*sizeof(int))); + handle_error(cudaMalloc((void**)&deviceIntermediates, numReductionThreads*sizeof(unsigned int))); + + handle_error(cudaMemcpy(deviceObjects, dimObjects[0], + numObjs*numCoords*sizeof(float), cudaMemcpyHostToDevice)); + handle_error(cudaMemcpy(deviceMembership, membership, + numObjs*sizeof(int), cudaMemcpyHostToDevice)); + + do { + handle_error(cudaMemcpy(deviceClusters, dimClusters[0], + numClusters*numCoords*sizeof(float), cudaMemcpyHostToDevice)); + + find_nearest_cluster + <<< numClusterBlocks, numThreadsPerClusterBlock, clusterBlockSharedDataSize >>> + (numCoords, numObjs, numClusters, + deviceObjects, deviceClusters, deviceMembership, deviceIntermediates); + + cudaDeviceSynchronize(); + + compute_delta <<< 1, numReductionThreads, reductionBlockSharedDataSize >>> + (deviceIntermediates, numClusterBlocks, numReductionThreads); + + cudaDeviceSynchronize(); + + int d; + handle_error(cudaMemcpy(&d, deviceIntermediates, + sizeof(int), cudaMemcpyDeviceToHost)); + delta = (float)d; + + handle_error(cudaMemcpy(membership, deviceMembership, + numObjs*sizeof(int), cudaMemcpyDeviceToHost)); + + for (i=0; i 0) + dimClusters[j][i] = newClusters[j][i] / newClusterSize[i]; + newClusters[j][i] = 0.0; /* set back to 0 */ + } + newClusterSize[i] = 0; /* set back to 0 */ + } + + delta /= numObjs; + } while (delta > threshold && loop++ < loops); + + + + /* allocate a 2D space for returning variable clusters[] (coordinates + of cluster centers) */ + malloc2D(clusters, numClusters, numCoords, float); + for (i = 0; i < numClusters; i++) { + for (j = 0; j < numCoords; j++) { + clusters[i][j] = dimClusters[j][i]; + } + } + + handle_error(cudaFree(deviceObjects)); + handle_error(cudaFree(deviceClusters)); + handle_error(cudaFree(deviceMembership)); + handle_error(cudaFree(deviceIntermediates)); + + free(dimObjects[0]); + free(dimObjects); + free(dimClusters[0]); + free(dimClusters); + free(newClusters[0]); + free(newClusters); + free(newClusterSize); + + return clusters; +} diff --git a/tira/cuda/sharedmem.cuh b/tira/cuda/sharedmem.cuh new file mode 100644 index 0000000..60db5e0 --- /dev/null +++ b/tira/cuda/sharedmem.cuh @@ -0,0 +1,88 @@ + +#ifndef STIM_CUDA_SHAREDMEM_H +#define STIM_CUDA_SHAREDMEM_H + +namespace stim{ + namespace cuda{ + + // Copies values from texture memory to shared memory, optimizing threads + template + __device__ void sharedMemcpy_tex2D(T* dest, cudaTextureObject_t src, + unsigned int x, unsigned int y, unsigned int X, unsigned int Y, + dim3 threadIdx, dim3 blockDim){ + + //calculate the number of iterations required for the copy + unsigned int xI, yI; + xI = X/blockDim.x + 1; //number of iterations along X + yI = Y/blockDim.y + 1; //number of iterations along Y + + //for each iteration + for(unsigned int xi = 0; xi < xI; xi++){ + for(unsigned int yi = 0; yi < yI; yi++){ + + //calculate the index into shared memory + unsigned int sx = xi * blockDim.x + threadIdx.x; + unsigned int sy = yi * blockDim.y + threadIdx.y; + + //calculate the index into the texture + unsigned int tx = x + sx; + unsigned int ty = y + sy; + + //perform the copy + if(sx < X && sy < Y) + dest[sy * X + sx] = tex2D(src, tx, ty); + } + } + } + + // Threaded copying of data on a CUDA device. + __device__ void threadedMemcpy(char* dest, char* src, size_t N, size_t tid, size_t nt){ + size_t I = N / nt + 1; //calculate the number of iterations required to make the copy + size_t xi = tid; //initialize the source and destination index to the thread ID + for(size_t i = 0; i < I; i++){ //for each iteration + if(xi < N) //if the index is within the copy region + dest[xi] = src[xi]; //perform the copy + xi += nt; + } + } + + /// Threaded copying of 2D data on a CUDA device + /// @param dest is a linear destination array of size nx * ny + /// @param nx is the size of the region to be copied along the X dimension + /// @param ny is the size of the region to be copied along the Y dimension + /// @param src is a 2D image stored as a linear array with a pitch of X + /// @param x is the x position in the source image where the copy is started + /// @param y is the y position in the source image where the copy is started + /// @param X is the number of bytes in a row of src + /// @param tid is a 1D id for the current thread + /// @param nt is the number of threads in the block + template + __device__ void threadedMemcpy2D(T* dest, size_t nx, size_t ny, + T* src, size_t x, size_t y, size_t sX, size_t sY, + size_t tid, size_t nt){ + + size_t vals = nx * ny; //calculate the total number of bytes to be copied + size_t I = vals / nt + 1; //calculate the number of iterations required to perform the copy + + size_t src_i, dest_i; + size_t dest_x, dest_y, src_x, src_y; + for(size_t i = 0; i < I; i++){ //for each iteration + dest_i = i * nt + tid; //calculate the index into the destination array + dest_y = dest_i / nx; + dest_x = dest_i - dest_y * nx; + + if(dest_y < ny && dest_x < nx){ + + src_x = x + dest_x; + src_y = y + dest_y; + + src_i = src_y * sX + src_x; + dest[dest_i] = src[src_i]; + } + } + } + } +} + + +#endif diff --git a/tira/cuda/spider_cost.cuh b/tira/cuda/spider_cost.cuh new file mode 100644 index 0000000..f655d43 --- /dev/null +++ b/tira/cuda/spider_cost.cuh @@ -0,0 +1,202 @@ +#ifndef STIM_SPIDER_COST_H +#define STIM_SPIDER_COST_H + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +namespace stim{ + namespace cuda + { + +// float* result; +// float* print; + + ///Initialization function, allocates the memory and passes the necessary + ///handles from OpenGL and Cuda. + ///@param DIM_Y --integer controlling how much memory to allocate. +// void initArray(int DIM_Y) +// { +// cudaMalloc( (void**) &print, DIM_Y*16*sizeof(float)); ///temporary +// cudaMalloc( (void**) &result, DIM_Y*sizeof(float)); +// } + + ///Deinit function that frees the memery used and releases the texture resource + ///back to OpenGL. +// void cleanUP() +// { +// cudaFree(result); +// cudaFree(print); ///temporary +// } + + ///A virtual representation of a uniform template. + ///Returns the value of the template pixel. + ///@param int x --location of a pixel. + __device__ + float Template(int x, int max_x) + { + if(x < max_x/6 || x > max_x*5/6 || (x > max_x*2/6 && x < max_x*4/6)) + { + return 1.0; + }else{ + return 0.0; + } + + } + + ///Find the difference of the given set of samples and the template + ///using cuda acceleration. + ///@param stim::cuda::cuda_texture t --stim texture that holds all the references + /// to the data. + ///@param float* result --a pointer to the memory that stores the result. + __global__ + //void get_diff (float *result) + void get_diff (cudaTextureObject_t texIn, float *result, int dx, int dy) + { +// __shared__ float shared[32][16]; + extern __shared__ float shared[]; + int x = threadIdx.x + blockIdx.x * blockDim.x; + int y = threadIdx.y + blockIdx.y * blockDim.y; + int x_t = threadIdx.x; + int y_t = threadIdx.y; + int idx = y_t*dx+x_t; + int g_idx = blockIdx.y; + + float valIn = tex2D(texIn, x, y)/255.0; + float valTemp = Template(x, dx); + +// print[idx] = abs(valIn); ///temporary + + shared[idx] = abs(valIn-valTemp); + + __syncthreads(); + + for(unsigned int step = blockDim.x/2; step >= 1; step >>= 1) + { + __syncthreads(); + if (x_t < step) + { +// shared[x_t][y_t] += shared[x_t + step][y_t]; + shared[idx] += shared[y_t*dx+x_t+step]; + } + __syncthreads(); + } + __syncthreads(); + + for(unsigned int step = blockDim.y/2; step >= 1; step >>= 1) + { + __syncthreads(); + if(y_t < step) + { +// shared[x_t][y_t] += shared[x_t][y_t + step]; + shared[idx] += shared[(y_t+step)*dx+x_t]; + } + __syncthreads(); + } + __syncthreads(); + if(x_t == 0 && y_t == 0) + result[g_idx] = shared[0]; + + + // //result[idx] = abs(valIn); + } + + + ///External access-point to the cuda function + ///@param GLuint texbufferID --GLtexture (most be contained in a framebuffer object) + /// that holds the data that will be handed to cuda. + ///@param GLenum texType --either GL_TEXTURE_1D, GL_TEXTURE_2D or GL_TEXTURE_3D + /// may work with other gl texture types, but untested. + ///@param DIM_Y, the number of samples in the template. + extern "C" + //stim::vec get_cost(GLint texbufferID, GLenum texType, int DIM_Y,int dx = 16, int dy = 8) + stim::vec get_cost(cudaTextureObject_t tObj, float* result, int DIM_Y,int dx = 16, int dy = 8) + { + + //Bind the Texture in GL and allow access to cuda. +// #ifdef TIMING +// gpuStartTimer(); +// #endif +// t.MapCudaTexture(texbufferID, texType); +// #ifdef TIMING +// std::cout << " " << gpuStopTimer(); +// #endif + + //initialize the return arrays. +// #ifdef TIMING +// gpuStartTimer(); +// #endif + float* output; + output = (float* ) malloc(DIM_Y*sizeof(float)); + + stim::vec ret(0, 0); +// initArray(DIM_Y); + + + //variables for finding the min. + float mini = 10000000000000000.0; + int idx = 0; +// #ifdef TIMING +// std::cout << " " << gpuStopTimer(); +// #endif + + //cuda launch variables. +// #ifdef TIMING +// gpuStartTimer(); +// #endif + dim3 numBlocks(1, DIM_Y); + dim3 threadsPerBlock(dx, dy); + + get_diff <<< numBlocks, threadsPerBlock, dx*dy*sizeof(float) >>> (tObj, result, dx, dy); + cudaDeviceSynchronize(); +// #ifdef TIMING +// std::cout << " " << gpuStopTimer(); +// #endif + +// #ifdef TIMING +// gpuStartTimer(); +// #endif + HANDLE_ERROR( + cudaMemcpy(output, result, DIM_Y*sizeof(float), cudaMemcpyDeviceToHost) + ); + + for( int i = 0; i(print, name.str(),16,218,0,256); + +// t.UnmapCudaTexture(); +// cleanUP(); + ret[0] = idx; ret[1] = (int) output[idx]; +// std::cout << "The cost is " << output[idx] << std::endl; + free(output); +// #ifdef TIMING +// std::cout << " " << gpuStopTimer() << std::endl; +// #endif + return ret; + } + + } +} + + +#endif diff --git a/tira/cuda/templates/chi_gradient.cuh b/tira/cuda/templates/chi_gradient.cuh new file mode 100644 index 0000000..cb4b225 --- /dev/null +++ b/tira/cuda/templates/chi_gradient.cuh @@ -0,0 +1,221 @@ +#ifndef STIM_CUDA_CHI_GRAD_H +#define STIM_CUDA_CHI_GRAD_H + +#include +#include +#include +#include +#include +#include + +#define PI 3.14159265358979 + +namespace stim{ + namespace cuda{ + + /// template parameter @param T is the data type + template + __global__ void cuda_chi_grad(T* copy, cudaTextureObject_t texObj, unsigned int w, unsigned int h, int r, unsigned int bin_n, unsigned int bin_size, float theta){ + + double theta_r = ((theta) * PI)/180; //change angle unit from degree to rad + float sum = 0; + unsigned int N = w * h; + + //change 1D index to 2D cordinates + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yj = blockIdx.y; + int idx = yj * w + xi; + int shareidx = threadIdx.x; + + extern __shared__ unsigned short bin[]; + + + if(xi < w && yj < h){ + + int gidx; + int hidx; + + //initialize histogram bin to zeros + for(int i = 0; i < bin_n; i++){ + + bin[shareidx * bin_n + i] = 0; + __syncthreads(); + + } + + //get the histogram of the first half of disc and store in bin + for (int y = yj - r; y <= yj + r; y++){ + for (int x = xi - r; x <= xi + r; x++){ + + if ((y - yj)*cos(theta_r) + (x - xi)*sin(theta_r) > 0){ + + gidx = (int) tex2D(texObj, (float)x/w, (float)y/h)/bin_size; + __syncthreads(); + + bin[shareidx * bin_n + gidx]++; + __syncthreads(); + + } + + else{} + } + } + + //initiallize the gbin + unsigned short* gbin = (unsigned short*) malloc(bin_n*sizeof(unsigned short)); + memset (gbin, 0, bin_n*sizeof(unsigned short)); + + //copy the histogram to gbin + for (unsigned int gi = 0; gi < bin_n; gi++){ + + gbin[gi] = bin[shareidx * bin_n + gi]; + + } + + //initialize histogram bin to zeros + for(int j = 0; j < bin_n; j++){ //initialize histogram bin to zeros + + bin[shareidx * bin_n + j] = 0; + __syncthreads(); + } + + //get the histogram of the second half of disc and store in bin + for (int y = yj - r; y <= yj + r; y++){ + for (int x = xi - r; x <= xi + r; x++){ + + if ((y - yj)*cos(theta_r) + (x - xi)*sin(theta_r) < 0){ + + hidx = (int) tex2D(texObj, (float)x/w, (float)y/h)/bin_size; + __syncthreads(); + + bin[shareidx * bin_n + hidx]++; + __syncthreads(); + + } + else{} + } + } + + //initiallize the gbin + unsigned short* hbin = (unsigned short*) malloc(bin_n*sizeof(unsigned short)); + memset (hbin, 0, bin_n*sizeof(unsigned short)); + + //copy the histogram to hbin + for (unsigned int hi = 0; hi < bin_n; hi++){ + + hbin[hi] = bin[shareidx * bin_n + hi]; + + } + + //compare gbin, hbin and calculate the chi distance + for (int k = 0; k < bin_n; k++){ + + float flag; // set flag to avoid zero denominator + + if ((gbin[k] + hbin[k]) == 0){ + flag = 1; + } + else { + flag = (gbin[k] + hbin[k]); + __syncthreads(); + } + + sum += (gbin[k] - hbin[k])*(gbin[k] - hbin[k])/flag; + __syncthreads(); + + } + + // return chi-distance for each pixel + copy[idx] = sum; + + free(gbin); + free(hbin); + } + } + + + template + void gpu_chi_grad(T* img, T* copy, unsigned int w, unsigned int h, int r, unsigned int bin_n, unsigned int bin_size, float theta){ + + unsigned long N = w * h; + + // Allocate CUDA array in device memory + + //define a channel descriptor for a single 32-bit channel + cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(32, 0, 0, 0, cudaChannelFormatKindFloat); + cudaArray* cuArray; //declare the cuda array + cudaMallocArray(&cuArray, &channelDesc, w, h); //allocate the cuda array + + // Copy the image data from global memory to the array + cudaMemcpyToArray(cuArray, 0, 0, img, N * sizeof(T), cudaMemcpyDeviceToDevice); + + // Specify texture + struct cudaResourceDesc resDesc; //create a resource descriptor + memset(&resDesc, 0, sizeof(resDesc)); //set all values to zero + resDesc.resType = cudaResourceTypeArray; //specify the resource descriptor type + resDesc.res.array.array = cuArray; //add a pointer to the cuda array + + // Specify texture object parameters + struct cudaTextureDesc texDesc; //create a texture descriptor + memset(&texDesc, 0, sizeof(texDesc)); //set all values in the texture descriptor to zero + texDesc.addressMode[0] = cudaAddressModeMirror; //use wrapping (around the edges) + texDesc.addressMode[1] = cudaAddressModeMirror; + texDesc.filterMode = cudaFilterModePoint; //use linear filtering + texDesc.readMode = cudaReadModeElementType; //reads data based on the element type (32-bit floats) + texDesc.normalizedCoords = 1; //using normalized coordinates + + // Create texture object + cudaTextureObject_t texObj = 0; + cudaCreateTextureObject(&texObj, &resDesc, &texDesc, NULL); + + //get the maximum number of threads per block for the CUDA device + int threads = stim::maxThreadsPerBlock(); + int sharemax = stim::sharedMemPerBlock(); //get the size of Shared memory available per block in bytes + unsigned int shared_bytes = threads * bin_n * sizeof(unsigned short); + + if(threads * bin_n > sharemax){ + + cout <<"Error: shared_bytes exceeds the max value."<<'\n'; + exit(1); + + } + + + //calculate the number of blocks + dim3 blocks(w / threads + 1, h); + + //call the kernel to do the multiplication + cuda_chi_grad <<< blocks, threads, shared_bytes >>>(copy, texObj, w, h, r, bin_n, bin_size, theta); + + } + + template + void cpu_chi_grad(T* img, T* cpu_copy, unsigned int w, unsigned int h, int r, unsigned int bin_n, unsigned int bin_size, float theta){ + + unsigned long N = w * h; + //allocate memory on the GPU for the array + T* gpu_img; + T* gpu_copy; + HANDLE_ERROR( cudaMalloc( &gpu_img, N * sizeof(T) ) ); + HANDLE_ERROR( cudaMalloc( &gpu_copy, N * sizeof(T) ) ); + + //copy the array to the GPU + HANDLE_ERROR( cudaMemcpy( gpu_img, img, N * sizeof(T), cudaMemcpyHostToDevice) ); + + //call the GPU version of this function + gpu_chi_grad(gpu_img, gpu_copy, w, h, r, bin_n, bin_size, theta); + + //copy the array back to the CPU + HANDLE_ERROR( cudaMemcpy( cpu_copy, gpu_copy, N * sizeof(T), cudaMemcpyDeviceToHost) ); + + //free allocated memory + cudaFree(gpu_img); + cudaFree(gpu_copy); + + } + + } +} + + +#endif \ No newline at end of file diff --git a/tira/cuda/templates/conv2.cuh b/tira/cuda/templates/conv2.cuh new file mode 100644 index 0000000..9d8c714 --- /dev/null +++ b/tira/cuda/templates/conv2.cuh @@ -0,0 +1,144 @@ +#ifndef STIM_CUDA_CONV2_H +#define STIM_CUDA_CONV2_H + +#include +#include +#include +#include +#include + +namespace stim{ + namespace cuda{ + + template + __global__ void cuda_conv2(T* mask, T* copy, cudaTextureObject_t texObj, unsigned int w, unsigned int h, unsigned int M){ + + + //the radius of mask + int r = (M - 1)/2; + + + //calculate the 1D index for this thread + //int idx = blockIdx.x * blockDim.x + threadIdx.x; + + //change 1D index to 2D cordinates + int i = blockIdx.x * blockDim.x + threadIdx.x; + int j = blockIdx.y; + + int idx = j * w + i; + //unsigned long N = w * h; + + if(i < w && j < h){ + + //copy[idx] = tex2D(texObj, i+100, j+100); + //return; + + tex2D(texObj, (float)i/w, (float)j/h); + + //allocate memory for result + T sum = 0; + + //for (unsigned int y = max(j - r, 0); y <= min(j + r, h - 1); y++){ + + //for (unsigned int x = max(i - r, 0); x <= min(i + r, w - 1); x++){ + + for (int y = j - r; y <= j + r; y++){ + + for (int x = i - r; x <= i + r; x++){ + + //idx to mask cordinates(xx, yy) + int xx = x - (i - r); + int yy = y - (j - r); + + sum += tex2D(texObj, (float)x/w, (float)y/h) * mask[yy * M + xx]; + } + } + copy[idx] = sum; + } + } + + + template + void gpu_conv2(T* img, T* mask, T* copy, unsigned int w, unsigned int h, unsigned M){ + + unsigned long N = w * h; + + // Allocate CUDA array in device memory + + //define a channel descriptor for a single 32-bit channel + cudaChannelFormatDesc channelDesc = + cudaCreateChannelDesc(32, 0, 0, 0, + cudaChannelFormatKindFloat); + cudaArray* cuArray; //declare the cuda array + cudaMallocArray(&cuArray, &channelDesc, w, h); //allocate the cuda array + + // Copy the image data from global memory to the array + cudaMemcpyToArray(cuArray, 0, 0, img, N * sizeof(T), + cudaMemcpyDeviceToDevice); + + // Specify texture + struct cudaResourceDesc resDesc; //create a resource descriptor + memset(&resDesc, 0, sizeof(resDesc)); //set all values to zero + resDesc.resType = cudaResourceTypeArray; //specify the resource descriptor type + resDesc.res.array.array = cuArray; //add a pointer to the cuda array + + // Specify texture object parameters + struct cudaTextureDesc texDesc; //create a texture descriptor + memset(&texDesc, 0, sizeof(texDesc)); //set all values in the texture descriptor to zero + texDesc.addressMode[0] = cudaAddressModeClamp; //use wrapping (around the edges) + texDesc.addressMode[1] = cudaAddressModeClamp; + texDesc.filterMode = cudaFilterModePoint; //use linear filtering + texDesc.readMode = cudaReadModeElementType; //reads data based on the element type (32-bit floats) + texDesc.normalizedCoords = 1; //using normalized coordinates + + // Create texture object + cudaTextureObject_t texObj = 0; + cudaCreateTextureObject(&texObj, &resDesc, &texDesc, NULL); + + //get the maximum number of threads per block for the CUDA device + int threads = stim::maxThreadsPerBlock(); + + //calculate the number of blocks + dim3 blocks(w / threads + 1, h); + + //call the kernel to do the multiplication + //cuda_conv2 <<< blocks, threads >>>(img, mask, copy, w, h, M); + cuda_conv2 <<< blocks, threads >>>(img, mask, copy, texObj, w, h, M); + cudaDestroyTextureObject(texObj); + cudaFreeArray(cuArray); + } + + template + void cpu_conv2(T* img, T* mask, T* cpu_copy, unsigned int w, unsigned int h, unsigned M){ + + unsigned long N = w * h; + //allocate memory on the GPU for the array + T* gpu_img; + T* gpu_mask; + T* gpu_copy; + HANDLE_ERROR( cudaMalloc( &gpu_img, N * sizeof(T) ) ); + HANDLE_ERROR( cudaMalloc( &gpu_mask, pow(M, 2) * sizeof(T) ) ); + HANDLE_ERROR( cudaMalloc( &gpu_copy, N * sizeof(T) ) ); + + //copy the array to the GPU + HANDLE_ERROR( cudaMemcpy( gpu_img, img, N * sizeof(T), cudaMemcpyHostToDevice) ); + HANDLE_ERROR( cudaMemcpy( gpu_mask, mask, pow(M, 2) * sizeof(T), cudaMemcpyHostToDevice) ); + + //call the GPU version of this function + gpu_conv2(gpu_img, gpu_mask ,gpu_copy, w, h, M); + + //copy the array back to the CPU + HANDLE_ERROR( cudaMemcpy( cpu_copy, gpu_copy, N * sizeof(T), cudaMemcpyDeviceToHost) ); + + //free allocated memory + cudaFree(gpu_img); + cudaFree(gpu_mask); + cudaFree(gpu_copy); + + } + + } +} + + +#endif diff --git a/tira/cuda/templates/conv2sep.cuh b/tira/cuda/templates/conv2sep.cuh new file mode 100644 index 0000000..26e4a05 --- /dev/null +++ b/tira/cuda/templates/conv2sep.cuh @@ -0,0 +1,262 @@ +#ifndef STIM_CUDA_CONV2SEP_H +#define STIM_CUDA_CONV2SEP_H + +#include +#include +#include +#include +#include +#include + +namespace stim{ + namespace cuda{ + + template + __global__ void conv2sep_0(T* out, cudaTextureObject_t in, unsigned int x, unsigned int y, + T* kernel0, unsigned int k0){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ T s[]; + + int kr = k0/2; //calculate the kernel radius + + //get a pointer to the gaussian in memory + T* g = (T*)&s[blockDim.x + 2 * kr]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + int byi = blockIdx.y; + + //copy the portion of the image necessary for this block to shared memory + //stim::cuda::sharedMemcpy_tex2D(s, in, bxi - kr, byi, 2 * kr + blockDim.x, 1, threadIdx, blockDim); + stim::cuda::sharedMemcpy_tex2D(s, in, bxi - kr, byi, 2 * kr + blockDim.x, 1, threadIdx, blockDim); + + //calculate the thread index + int ti = threadIdx.x; + + //calculate the spatial coordinate for this thread + int xi = bxi + ti; + int yi = byi; + + + //use the first 2kr+1 threads to transfer the kernel to shared memory + if(ti < k0){ + g[ti] = kernel0[ti]; + } + + //make sure that all writing to shared memory is done before continuing + __syncthreads(); + + //if the current pixel is outside of the image + if(xi >= x || yi >= y) + return; + + + //calculate the coordinates of the current thread in shared memory + int si = ti + kr; + + T sum = 0; //running weighted sum across the kernel + + + //for each element of the kernel + for(int ki = -kr; ki <= kr; ki++){ + sum += s[si + ki] * g[ki + kr]; + } + + //calculate the 1D image index for this thread + unsigned int i = byi * x + xi; + + //output the result to global memory + out[i] = sum; + } + + template + __global__ void conv2sep_1(T* out, cudaTextureObject_t in, unsigned int x, unsigned int y, + T* kernel0, unsigned int k0){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ T s[]; + + int kr = k0/2; //calculate the kernel radius + + //get a pointer to the gaussian in memory + T* g = (T*)&s[blockDim.y + 2 * kr]; + + //calculate the start point for this block + int bxi = blockIdx.x; + int byi = blockIdx.y * blockDim.y; + + //copy the portion of the image necessary for this block to shared memory + //stim::cuda::sharedMemcpy_tex2D(s, in, bxi, byi - kr, 1, 2 * kr + blockDim.y, threadIdx, blockDim); + stim::cuda::sharedMemcpy_tex2D(s, in, bxi, byi - kr, 1, 2 * kr + blockDim.y, threadIdx, blockDim); + + //calculate the thread index + int ti = threadIdx.y; + + //calculate the spatial coordinate for this thread + int xi = bxi; + int yi = byi + ti; + + + //use the first 2kr+1 threads to transfer the kernel to shared memory + if(ti < k0){ + g[ti] = kernel0[ti]; + } + + //make sure that all writing to shared memory is done before continuing + __syncthreads(); + + //if the current pixel is outside of the image + if(xi > x || yi > y) + return; + + + //calculate the coordinates of the current thread in shared memory + int si = ti + kr; + + T sum = 0; //running weighted sum across the kernel + + + //for each element of the kernel + for(int ki = -kr; ki <= kr; ki++){ + sum += g[ki + kr] * s[si + ki]; + } + + //calculate the 1D image index for this thread + unsigned int i = yi * x + xi; + + //output the result to global memory + out[i] = sum; + } + + template + void tex_conv2sep(T* out, unsigned int x, unsigned int y, + cudaTextureObject_t texObj, cudaArray* cuArray, + T* kernel0, unsigned int k0, + T* kernel1, unsigned int k1){ + + //get the maximum number of threads per block for the CUDA device + int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + + //calculate the number of blocks + dim3 blocks(x / threads.x + 1, y); + + //calculate the shared memory used in the kernel + unsigned int pixel_bytes = max_threads * sizeof(T); //bytes devoted to pixel data being processed + unsigned int apron_bytes = k0/2 * sizeof(T); //bytes devoted to the apron on each side of the window + unsigned int gaussian_bytes = k0 * sizeof(T); //bytes devoted to memory used to store the pre-computed Gaussian window + unsigned int shared_bytes = pixel_bytes + 2 * apron_bytes + gaussian_bytes; //total number of bytes shared memory used + + //blur the image along the x-axis + conv2sep_0 <<< blocks, threads, shared_bytes >>>(out, texObj, x, y, kernel0, k0); + + // Copy the x-blurred data from global memory to the texture + cudaMemcpyToArray(cuArray, 0, 0, out, x * y * sizeof(T), + cudaMemcpyDeviceToDevice); + + //transpose the block and thread dimensions + threads.x = 1; + threads.y = max_threads; + blocks.x = x; + blocks.y = y / threads.y + 1; + + //blur the image along the y-axis + conv2sep_1 <<< blocks, threads, shared_bytes >>>(out, texObj, x, y, kernel1, k1); + + } + + template + void gpu_conv2sep(T* image, unsigned int x, unsigned int y, + T* kernel0, unsigned int k0, + T* kernel1, unsigned int k1){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = sizeof(T) * pixels; + + // Allocate CUDA array in device memory + + //define a channel descriptor for a single 32-bit channel + cudaChannelFormatDesc channelDesc = + cudaCreateChannelDesc(32, 0, 0, 0, + cudaChannelFormatKindFloat); + cudaArray* cuArray; //declare the cuda array + cudaMallocArray(&cuArray, &channelDesc, x, y); //allocate the cuda array + + // Copy the image data from global memory to the array + cudaMemcpyToArray(cuArray, 0, 0, image, bytes, + cudaMemcpyDeviceToDevice); + + // Specify texture + struct cudaResourceDesc resDesc; //create a resource descriptor + memset(&resDesc, 0, sizeof(resDesc)); //set all values to zero + resDesc.resType = cudaResourceTypeArray; //specify the resource descriptor type + resDesc.res.array.array = cuArray; //add a pointer to the cuda array + + // Specify texture object parameters + struct cudaTextureDesc texDesc; //create a texture descriptor + memset(&texDesc, 0, sizeof(texDesc)); //set all values in the texture descriptor to zero + texDesc.addressMode[0] = cudaAddressModeWrap; //use wrapping (around the edges) + texDesc.addressMode[1] = cudaAddressModeWrap; + texDesc.filterMode = cudaFilterModePoint; //use linear filtering + texDesc.readMode = cudaReadModeElementType; //reads data based on the element type (32-bit floats) + texDesc.normalizedCoords = 0; //not using normalized coordinates + + // Create texture object + cudaTextureObject_t texObj = 0; + cudaCreateTextureObject(&texObj, &resDesc, &texDesc, NULL); + + //call the texture version of the separable convolution function + tex_conv2sep(image, x, y, texObj, cuArray, kernel0, k0, kernel1, k1); + + //free allocated memory + cudaFree(cuArray); + + cudaDestroyTextureObject(texObj); + + } + + /// Applies a Gaussian blur to a 2D image stored on the CPU + template + void cpu_conv2sep(T* image, unsigned int x, unsigned int y, + T* kernel0, unsigned int k0, + T* kernel1, unsigned int k1){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = sizeof(T) * pixels; + + //---------Allocate Image--------- + //allocate space on the GPU for the image + T* gpuI0; + HANDLE_ERROR(cudaMalloc(&gpuI0, bytes)); + + //copy the image data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuI0, image, bytes, cudaMemcpyHostToDevice)); + + //---------Allocate Kernel-------- + //allocate and copy the 0 (x) kernel + T* gpuK0; + HANDLE_ERROR(cudaMalloc(&gpuK0, k0 * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpuK0, kernel0, k0 * sizeof(T), cudaMemcpyHostToDevice)); + + //allocate and copy the 1 (y) kernel + T* gpuK1; + HANDLE_ERROR(cudaMalloc(&gpuK1, k1 * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpuK1, kernel1, k1 * sizeof(T), cudaMemcpyHostToDevice)); + + //run the GPU-based version of the algorithm + gpu_conv2sep(gpuI0, x, y, gpuK0, k0, gpuK1, k1); + + //copy the image data from the device + cudaMemcpy(image, gpuI0, bytes, cudaMemcpyDeviceToHost); + + //free allocated memory + cudaFree(gpuI0); + } + + }; +}; + +#endif diff --git a/tira/cuda/templates/gaussian_blur.cuh b/tira/cuda/templates/gaussian_blur.cuh new file mode 100644 index 0000000..1165bd7 --- /dev/null +++ b/tira/cuda/templates/gaussian_blur.cuh @@ -0,0 +1,91 @@ +#ifndef STIM_CUDA_GAUSSIAN_BLUR_H +#define STIM_CUDA_GAUSSIAN_BLUR_H + +#include +#include +#include +#include +#include //GPU-based separable convolution algorithm + + +namespace stim{ + namespace cuda{ + + template + void gen_gaussian(T* out, T sigma, unsigned int width){ + + //fill the kernel with a gaussian + for(unsigned int xi = 0; xi < width; xi++){ + + float x = (float)xi - (float)(width/2); //calculate the x position of the gaussian + float g = 1.0 / (sigma * sqrt(2 * 3.14159)) * exp( - (x*x) / (2*sigma*sigma) ); + out[xi] = g; + } + + } + + template + void tex_gaussian_blur2(T* out, T sigma, unsigned int x, unsigned int y, cudaTextureObject_t texObj, cudaArray* cuArray){ + + //allocate space for the kernel + unsigned int kwidth = sigma * 8 + 1; + float* kernel0 = (float*) malloc( kwidth * sizeof(float) ); + + //fill the kernel with a gaussian + gen_gaussian(kernel0, sigma, kwidth); + + //copy the kernel to the GPU + T* gpuKernel0; + HANDLE_ERROR(cudaMalloc(&gpuKernel0, kwidth*sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpuKernel0, kernel0, kwidth * sizeof(T), cudaMemcpyHostToDevice)); + + //perform the gaussian blur as a separable convolution + stim::cuda::tex_conv2sep(out, x, y, texObj, cuArray, gpuKernel0, kwidth, gpuKernel0, kwidth); + + HANDLE_ERROR(cudaFree(gpuKernel0)); + free(kernel0); + + } + + template + void gpu_gaussian_blur2(T* image, T sigma, unsigned int x, unsigned int y){ + + //allocate space for the kernel + unsigned int kwidth = sigma * 8 + 1; + float* kernel0 = (float*) malloc( kwidth * sizeof(float) ); + + //fill the kernel with a gaussian + gen_gaussian(kernel0, sigma, kwidth); + + //copy the kernel to the GPU + T* gpuKernel0; + HANDLE_ERROR(cudaMalloc(&gpuKernel0, kwidth*sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpuKernel0, kernel0, kwidth * sizeof(T), cudaMemcpyHostToDevice)); + + //perform the gaussian blur as a separable convolution + stim::cuda::gpu_conv2sep(image, x, y, gpuKernel0, kwidth, gpuKernel0, kwidth); + + HANDLE_ERROR(cudaFree(gpuKernel0)); + + } + + /// Applies a Gaussian blur to a 2D image stored on the CPU + template + void cpu_gaussian_blur2(T* image, T sigma, unsigned int x, unsigned int y){ + + //allocate space for the kernel + unsigned int kwidth = sigma * 8 + 1; + float* kernel0 = (float*) malloc( kwidth * sizeof(float) ); + + //fill the kernel with a gaussian + gen_gaussian(kernel0, sigma, kwidth); + + //perform the gaussian blur as a separable convolution + stim::cuda::cpu_conv2sep(image, x, y, kernel0, kwidth, kernel0, kwidth); + + } + + }; +}; + +#endif diff --git a/tira/cuda/templates/gradient.cuh b/tira/cuda/templates/gradient.cuh new file mode 100644 index 0000000..c3816ec --- /dev/null +++ b/tira/cuda/templates/gradient.cuh @@ -0,0 +1,96 @@ +#ifndef STIM_CUDA_GRADIENT_H +#define STIM_CUDA_GRADIENT_H + +#include +#include +#include + +namespace stim{ + namespace cuda{ + + template + __global__ void gradient_2d(T* out, T* in, size_t x, size_t y){ + + + // calculate the 2D coordinates for this current thread. + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + size_t i = yi * x + xi; + + //return if the pixel is outside of the image + if(xi >= x || yi >= y) return; + + //calculate indices for the forward difference + size_t i_xp = yi * x + (xi + 1); + size_t i_yp = (yi + 1) * x + xi; + + //calculate indices for the backward difference + size_t i_xn = yi * x + (xi - 1); + size_t i_yn = (yi - 1) * x + xi; + + //use forward differences if a coordinate is zero + if(xi == 0) + out[i * 2 + 0] = in[i_xp] - in[i]; + else if (xi == x - 1) + out[i * 2 + 0] = in[i] - in[i_xn]; + else + out[i * 2 + 0] = (in[i_xp] - in[i_xn]) / 2; + + if(yi == 0) + out[i * 2 + 1] = in[i_yp] - in[i]; + else if(yi == y-1) + out[i * 2 + 1] = in[i] - in[i_yn]; + else + out[i * 2 + 1] = (in[i_yp] - in[i_yn]) / 2; + + } + + template + void gpu_gradient_2d(T* gpuGrad, T* gpuI, size_t x, size_t y){ + + //get the maximum number of threads per block for the CUDA device + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks((unsigned int)(x/threads.x) + 1 , (unsigned int)y); + + + //call the GPU kernel to determine the gradient + gradient_2d <<< blocks, threads >>>(gpuGrad, gpuI, x, y); + + } + + template + void cpu_gradient_2d(T* out, T* in, unsigned int x, unsigned int y){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = pixels * sizeof(T); + + //allocate space on the GPU for the input image + T* gpuIn; + HANDLE_ERROR(cudaMalloc(&gpuIn, bytes)); + + //copy the image data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuIn, in, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the output gradient image + T* gpuOut; + cudaMalloc(&gpuOut, bytes * 2); //the output image will have two channels (x, y) + + //call the GPU version of this function + gpu_gradient_2d(gpuOut, gpuIn, x, y); + + //copy the results to the CPU + cudaMemcpy(out, gpuOut, bytes * 2, cudaMemcpyDeviceToHost); + + //free allocated memory + cudaFree(gpuOut); + cudaFree(gpuIn); + } + + } +} + + +#endif \ No newline at end of file diff --git a/tira/cuda/testKernel.cuh b/tira/cuda/testKernel.cuh new file mode 100644 index 0000000..e7a61ad --- /dev/null +++ b/tira/cuda/testKernel.cuh @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + float* print; + + ///Initialization function, allocates the memory and passes the necessary + ///handles from OpenGL and Cuda. + ///@param DIM_Y --integer controlling how much memory to allocate. + void initArray(int x, int y) + { + cudaMalloc( (void**) &print, x*y*sizeof(float)); ///temporary + } + + ///Deinit function that frees the memery used and releases the texture resource + ///back to OpenGL. + void cleanUP() + { + cudaFree(print); ///temporary + } + + __device__ + float templ(int x, int max_x) + { + if(x < max_x/6 || x > max_x*5/6 || (x > max_x*2/6 && x < max_x*4/6)) + { + return 1.0; + }else{ + return 0.0; + } + } + + ///Find the difference of the given set of samples and the template + ///using cuda acceleration. + ///@param stim::cuda::cuda_texture t --stim texture that holds all the references + /// to the data. + ///@param float* result --a pointer to the memory that stores the result. + __global__ + //void get_diff (float *result) + void get_diff (cudaTextureObject_t texIn, float *print, int dx) + { + int x = threadIdx.x + blockIdx.x * blockDim.x; + int y = threadIdx.y + blockIdx.y * blockDim.y; + int idx = y*dx+x; + // int idx = y*16+x; + + float valIn = tex2D(texIn, x, y); + float templa = templ(x, 32)*255.0; + //print[idx] = abs(valIn-templa); ///temporary + print[idx] = abs(valIn); + //print[idx] = abs(templa); ///temporary + + } + + ///Find the difference of the given set of samples and the template + ///using cuda acceleration. + ///@param stim::cuda::cuda_texture t --stim texture that holds all the references + /// to the data. + ///@param float* result --a pointer to the memory that stores the result. + __global__ + //void get_diff (float *result) + void get_diff2 (cudaTextureObject_t texIn, float *print, int dx) + { + int x = threadIdx.x + blockIdx.x * blockDim.x; + int y = threadIdx.y + blockIdx.y * blockDim.y; + int idx = y*dx+x; + // int idx = y*16+x; + + float valIn = tex2D(texIn, x, y); + print[idx] = abs(valIn); ///temporary + + } + + void test(cudaTextureObject_t tObj, int x, int y, std::string nam) + { + + //Bind the Texture in GL and allow access to cuda. + + //initialize the return arrays. + + initArray(x,y); + dim3 numBlocks(1, y); + dim3 threadsPerBlock(x, 1); + int max_threads = stim::maxThreadsPerBlock(); + //dim3 threads(max_threads, 1); + //dim3 blocks(x / threads.x + 1, y); + //dim3 numBlocks(2, 2); + //dim3 threadsPerBlock(8, 108); + + +// get_diff <<< blocks, threads >>> (tx.getTexture(), print); + get_diff <<< numBlocks, threadsPerBlock >>> (tObj, print, x); + + cudaDeviceSynchronize(); + stringstream name; //for debugging + name << nam.c_str(); + stim::gpu2image(print, name.str(),x,y,0,255); + + cleanUP(); + } + + + void test(cudaTextureObject_t tObj, int x, int y, std::string nam, int iter) + { + + //Bind the Texture in GL and allow access to cuda. + + //initialize the return arrays. + + initArray(x,y); + dim3 numBlocks(1, y); + dim3 threadsPerBlock(x, 1); + int max_threads = stim::maxThreadsPerBlock(); + //dim3 threads(max_threads, 1); + //dim3 blocks(x / threads.x + 1, y); + //dim3 numBlocks(2, 2); + //dim3 threadsPerBlock(8, 108); + + +// get_diff <<< blocks, threads >>> (tx.getTexture(), print); + get_diff2 <<< numBlocks, threadsPerBlock >>> (tObj, print, x); + + cudaDeviceSynchronize(); + stringstream name; //for debugging + name << nam.c_str(); + stim::gpu2image(print, name.str(),x,y,0,255); + + cleanUP(); + } diff --git a/tira/envi/agilent_binary.h b/tira/envi/agilent_binary.h new file mode 100644 index 0000000..68867f3 --- /dev/null +++ b/tira/envi/agilent_binary.h @@ -0,0 +1,358 @@ +//make sure that this header file is only loaded once +#ifndef STIM_AGILENT_BINARY_H +#define STIM_AGILENT_BINARY_H + +#include +#include +#include +#include +#include + +//CUDA +//#ifdef CUDA_FOUND +#include +#include "cufft.h" +#include +#include +//#endif + +namespace stim{ + +template +class agilent_binary{ + +protected: + std::string fname; + T* ptr; //pointer to the image data + size_t R[3]; //size of the binary image in X, Y, and Z + static const size_t header = 1020; //header size + double Z[2]; //range of z values (position or wavelength) + +public: + size_t size(){ + return (size_t)R[0] * (size_t)R[1] * (size_t)R[2]; + } + + size_t bytes(){ + return size() * sizeof(T); + } + void alloc(){ + if (ptr != NULL) free(ptr); + ptr = NULL; + ptr = (T*) malloc(bytes()); + } + void alloc(size_t x, size_t y, size_t z){ + R[0] = x; + R[1] = y; + R[2] = z; + alloc(); + } + + char* data() { + return (char*)ptr; + } + + size_t dim(size_t i){ + return R[i]; + } + + /// Create a deep copy of an agileng_binary object + void deep_copy(agilent_binary* dst, const agilent_binary* src){ + dst->alloc(src->R[0], src->R[1], src->R[2]); //allocate memory + memcpy(dst->ptr, src->ptr, bytes()); //copy the data + memcpy(dst->Z, src->Z, sizeof(double) * 2); //copy the data z range + } + + /// Default constructor, sets the resolution to zero and the data pointer to NULL + agilent_binary(){ + ptr = NULL; + memset(R, 0, sizeof(size_t) * 3); //set the resolution to zero + memset(Z, 0, sizeof(double) * 2); + } + + /// Constructor with resolution + agilent_binary(size_t x, size_t y, size_t z){ + ptr = NULL; + alloc(x, y, z); + memset(Z, 0, sizeof(double) * 2); + } + + /// Constructor with filename + agilent_binary(std::string filename){ + ptr = NULL; + memset(Z, 0, sizeof(double) * 2); + load(filename); + } + + /// Copy constructor + agilent_binary(const agilent_binary &obj){ + ptr = NULL; + deep_copy(this, &obj); + } + + agilent_binary& operator=(const agilent_binary rhs){ + if(this != &rhs){ //check for self-assignment + deep_copy(this, &rhs); //make a deep copy + } + return *this; //return the result + } + + operator bool() { + if (R[0] == 0 || R[1] == 0 || R[2] == 0) return false; + else return true; + } + + ~agilent_binary(){ + if(ptr != NULL) + free(ptr); + } + + void load(std::string filename){ + if(ptr != NULL) free(ptr); //if memory has been allocated, free it + ptr = NULL; + + fname = filename; //save the filename + + short x, y, z; + + std::ifstream infile(fname, std::ios::binary); //open the input file + if (infile) { + infile.seekg(9, std::ios::beg); //seek past 9 bytes from the beginning of the file + + infile.read((char*)(&z), 2); //read two bytes of data (the number of samples is stored as a 16-bit integer) + + infile.seekg(13, std::ios::cur); //skip another 13 bytes + infile.read((char*)(&x), 2); //read the X and Y dimensions + infile.read((char*)(&y), 2); + + infile.seekg(header, std::ios::beg); //seek to the start of the data + + alloc(x, y, z); //allocate the data + infile.read((char*)ptr, bytes()); //read the data + infile.close(); //close the file + Z[0] = 1; + Z[1] = (double)R[2]; + } + } + + void save(std::string filename){ + std::ofstream outfile(filename, std::ios::binary); //create an output file + + char zero = 0; + for(size_t i = 0; i < 9; i++) outfile.write(&zero, 1); //write 9 zeros + outfile.write((char*)&R[2], 2); + for(size_t i = 0; i < 13; i++) outfile.write(&zero, 1); //write 13 zeros + outfile.write((char*)&R[0], 2); + outfile.write((char*)&R[1], 2); + for(size_t i = 0; i < 992; i++) outfile.write(&zero, 1); //write 992 zeros + + size_t b = bytes(); + outfile.write((char*)ptr, b); //write the data to the output file + outfile.close(); + } + + stim::envi_header create_header(){ + stim::envi_header header; + header.samples = R[0]; + header.lines = R[1]; + header.bands = R[2]; + + double z_delta = (double)(Z[1] - Z[0]) / (double)(R[2] - 1); + header.wavelength.resize(R[2]); + for(size_t i = 0; i < R[2]; i++) + header.wavelength[i] = i * z_delta + Z[0]; + + return header; + } + + /// Subtract the mean from each pixel. Generally used for centering an interferogram. + void meancenter(){ + size_t Z = R[2]; //store the number of bands + size_t XY = R[0] * R[1]; //store the number of pixels in the image + T sum = (T)0; + T mean; + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + sum = 0; + for(size_t z = 0; z < Z; z++){ //for each band + sum += ptr[ z * XY + xy ]; //add the band value to a running sum + } + mean = sum / (T)Z; //calculate the pixel mean + for(size_t z = 0; z < Z; z++){ + ptr[ z * XY + xy ] -= mean; //subtract the mean from each band + } + } + } + + /// adds n bands of zero padding to the end of the file + void zeropad(size_t n){ + size_t newZ = R[2] + n; + T* temp = (T*) calloc(R[0] * R[1] * newZ, sizeof(T)); //allocate space for the new image + memcpy(temp, ptr, size() * sizeof(T)); //copy the old data to the new image + + free(ptr); //free the old data + ptr = temp; //swap in the new data + R[2] = newZ; //set the z-dimension to the new zero value + } + + //pads to the nearest power-of-two + void zeropad(){ + size_t newZ = (size_t)pow(2, ceil(log(R[2])/log(2))); //find the nearest power-of-two + size_t n = newZ - R[2]; //calculate the number of bands to add + zeropad(n); //add the padding + } + + /// Calculate the absorbance spectrum from the transmission spectrum given a background + void absorbance(stim::agilent_binary* background){ + size_t N = size(); //calculate the number of values to be ratioed + if(N != background->size()){ + std::cerr<<"ERROR in stim::agilent_binary::absorbance() - transmission image size doesn't match background"<ptr[i]); + } + + //crops the image down to a set number of samples + void crop(size_t n) { + if (n < R[2]) { //if the requested size is smaller than the image + R[2] = n; //update the number of bands + T* old_ptr = ptr; //store the old pointer + alloc(); //allocate space for the new image + memcpy(ptr, old_ptr, bytes()); //copy the old data to the new image + free(old_ptr); //free the old data + } + } + +//#ifdef CUDA_FOUND + /// Perform an FFT and return a binary file with bands in the specified range + agilent_binary fft(double band_min, double band_max, double ELWN = 15798, int UDR = 2, int device = 0){ + auto total_start = std::chrono::high_resolution_clock::now(); + + auto start = std::chrono::high_resolution_clock::now(); + T* cpu_data = (T*) malloc( bytes() ); //allocate space for the transposed data + for(size_t b = 0; b < R[2]; b++){ + for(size_t x = 0; x < R[0] * R[1]; x++){ + cpu_data[x * R[2] + b] = ptr[b * R[0] * R[1] + x]; + } + } + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = end-start; + // std::cout << "Transpose data: " << diff.count() << " s\n"; + + start = std::chrono::high_resolution_clock::now(); + if (device >= 0) { //if a CUDA device is specified + int dev_count; + HANDLE_ERROR(cudaGetDeviceCount(&dev_count)); //get the number of CUDA devices + //std::cout << "Number of CUDA devices: " << dev_count << std::endl; //output the number of CUDA devices + cudaDeviceProp prop; + //std::cout << "CUDA devices----" << std::endl; + for (int d = 0; d < dev_count; d++) { //for each CUDA device + cudaGetDeviceProperties(&prop, d); //get the property of the first device + //float cc = prop.major + prop.minor / 10.0f; //calculate the compute capability + //std::cout << d << ": [" << prop.major << "." << prop.minor << "] " << prop.name << std::endl; //display the device information + //if(cc > best_device_cc){ + // best_device_cc = cc; //if this is better than the previous device, use it + // best_device_id = d; + //} + } + if (dev_count > 0 && dev_count > device) { //if the first device is not an emulator + cudaGetDeviceProperties(&prop, device); //get the property of the requested CUDA device + if (prop.major != 9999) { + //std::cout << "Using device " << device << std::endl; + HANDLE_ERROR(cudaSetDevice(device)); + } + } + } + cufftHandle plan; //allocate space for a cufft plan + cufftReal* gpu_data; //create a pointer to the data + size_t batch = R[0] * R[1]; //calculate the batch size (X * Y) + HANDLE_ERROR(cudaMalloc((void**)&gpu_data, bytes())); //allocate space on the GPU + HANDLE_ERROR(cudaMemcpy(gpu_data, cpu_data, bytes(), cudaMemcpyHostToDevice)); //copy the data to the GPU + cufftComplex* gpu_fft; + HANDLE_ERROR(cudaMalloc((void**)&gpu_fft, R[0] * R[1] * (R[2]/2 + 1) * sizeof(cufftComplex))); + end = std::chrono::high_resolution_clock::now(); + diff = end-start; + //std::cout << "Allocate/copy: " << diff.count() << " s\n"; + + start = std::chrono::high_resolution_clock::now(); + int N[1]; //create an array with the interferogram size (required for cuFFT input) + N[0] = (int)R[2]; //set the only array value to the interferogram size + if(cufftPlanMany(&plan, 1, N, NULL, 1, (int)R[2], NULL, 1, (int)R[2], CUFFT_R2C, (int)batch) != CUFFT_SUCCESS){ + std::cout<<"cuFFT Error: unable to create 1D plan."<* cpu_fft = (std::complex*) malloc( R[0] * R[1] * (R[2]/2+1) * sizeof(std::complex) ); + HANDLE_ERROR(cudaMemcpy(cpu_fft, gpu_fft, R[0] * R[1] * (R[2]/2+1) * sizeof(cufftComplex), cudaMemcpyDeviceToHost)); //copy data from the host to the device + + //double int_delta = 0.00012656; //interferogram sample spacing in centimeters + double int_delta = (1.0 / ELWN) * ((double)UDR / 2.0); //calculate the interferogram spacing + double int_length = int_delta * R[2]; //interferogram length in centimeters + double fft_delta = 1/int_length; //spectrum spacing (in inverse centimeters, wavenumber) + double fft_max = fft_delta * R[2]/2; //get the maximum wavenumber value supported by the specified number of interferogram samples + + if(band_max > fft_max) band_max = fft_max; //the user gave a band outside of the FFT range, reset the band to the maximum available + if (band_min < 0) band_min = 0; + + size_t start_i = (size_t)std::ceil(band_min / fft_delta); //calculate the first band to store + size_t size_i = (size_t)std::floor(band_max / fft_delta) - start_i; //calculate the number of bands to store + size_t end_i = start_i + size_i; //last band number + agilent_binary result(R[0], R[1], size_i); + result.Z[0] = start_i * fft_delta; //set the range for the FFT result + result.Z[1] = end_i * fft_delta; + + for(size_t b = start_i; b < end_i; b++){ + for(size_t x = 0; x < R[0] * R[1]; x++){ + result.ptr[(b - start_i) * R[0] * R[1] + x] = abs(cpu_fft[x * (R[2]/2+1) + b]); + } + } + end = std::chrono::high_resolution_clock::now(); + diff = end-start; + //std::cout << "Transpose/Crop: " << diff.count() << " s\n"; + + auto total_end = std::chrono::high_resolution_clock::now(); + diff = total_end-total_start; + + cufftDestroy(plan); + HANDLE_ERROR(cudaFree(gpu_data)); + HANDLE_ERROR(cudaFree(gpu_fft)); + free(cpu_data); + free(cpu_fft); + + return result; + } + + //saves the binary as an ENVI file with a BIP interleave format + int bip(T* bip_ptr){ + //std::ofstream out(outfile.c_str(), std::ios::binary); //create a binary file stream for output + size_t XY = R[0] * R[1]; + size_t B = R[2]; + size_t b; + + for(size_t xy = 0; xy < XY; xy++){ + for(b = 0; b < B; b++){ + bip_ptr[xy * B + b] = ptr[b * XY + xy]; + } + } + return 0; + } +//#endif + +}; + +} + +#endif \ No newline at end of file diff --git a/tira/envi/bil.h b/tira/envi/bil.h new file mode 100644 index 0000000..b45dd30 --- /dev/null +++ b/tira/envi/bil.h @@ -0,0 +1,1542 @@ +#ifndef STIM_BIL_H +#define STIM_BIL_H + +#include "../envi/envi_header.h" +#include "../envi/hsi.h" +#include "../math/fd_coefficients.h" +#include +#include +#include +#include + +namespace stim{ + +/** + The BIL class represents a 3-dimensional binary file stored using band interleaved by line (BIL) image encoding. The binary file is stored + such that X-Z "frames" are stored sequentially to form an image stack along the y-axis. When accessing the data sequentially on disk, + the dimensions read, from fastest to slowest, are X, Z, Y. + + This class is optimized for data streaming, and therefore supports extremely large (terabyte-scale) files. Data is loaded from disk + on request. Functions used to access data are written to support efficient reading. +*/ + +template + +class bil: public hsi { + +protected: + + + using hsi::w; //use the wavelength array in stim::hsi + using hsi::nnz; + using binary::progress; + using hsi::X; + using hsi::Y; + using hsi::Z; + +public: + + using binary::open; + using binary::file; + using binary::R; + + bil(){ hsi::init_bil(); } + + /// Open a data file for reading using the class interface. + + /// @param filename is the name of the binary file on disk + /// @param X is the number of samples along dimension 1 + /// @param Y is the number of samples (lines) along dimension 2 + /// @param B is the number of samples (bands) along dimension 3 + /// @param header_offset is the number of bytes (if any) in the binary header + /// @param wavelengths is an optional STL vector of size B specifying a numerical label for each band + bool open(std::string filename, + unsigned long long X, + unsigned long long Y, + unsigned long long B, + unsigned long long header_offset, + std::vector wavelengths, + stim::iotype io = stim::io_in){ + + w = wavelengths; + + return open(filename, vec(X, B, Y), header_offset, io); + + } + + /// Retrieve a single band (based on index) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param page <= B is the integer number of the band to be copied. + bool band_index( T * p, unsigned long long page, bool PROGRESS = false){ + //return binary::read_plane_1(p, page); + + if(PROGRESS) progress = 0; + unsigned long long L = X() * sizeof(T); //caculate the number of bytes in a sample line + unsigned long long jump = X() * (Z() - 1) * sizeof(T); + + if (page >= Z()){ //make sure the bank number is right + std::cout<<"ERROR: page out of range"< wavelength ){ + band_index(p, page, PROGRESS); + return true; + } + + while( w[page] < wavelength ) + { + page++; + //if wavelength is larger than the last wavelength in header file + if (page == Z()) { + band_index(p, Z()-1, PROGRESS); + return true; + } + } + if ( wavelength < w[page] ) + { + T * p1; + T * p2; + p1=(T*)malloc(S); //memory allocation + p2=(T*)malloc(S); + band_index(p1, page - 1); + band_index(p2, page, PROGRESS); + for(unsigned long long i=0; i < XY; i++){ + double r = (wavelength - w[page-1]) / (w[page] - w[page-1]); + p[i] = (T)(((double)p2[i] - (double)p1[i]) * r + (double)p1[i]); + } + free(p1); + free(p2); + } + else //if the wavelength is equal to a wavelength in header file + { + band_index(p, page, PROGRESS); + } + + return true; + } + + /// Retrieves a band of x values from a given xz plane. + + /// @param p is a pointer to pre-allocated memory at least Z * sizeof(T) in size + /// @param c is a pointer to an existing XZ plane (size X*Z*sizeof(T)) + /// @param wavelength is the wavelength to retrieve + bool read_x_from_xz(T* p, T* c, double wavelength) + { + unsigned long long B = Z(); + unsigned long long L = X() * sizeof(T); + + unsigned long long page=0; //samples around the wavelength + T * p1; + T * p2; + + //get the bands numbers around the wavelength + + //if wavelength is smaller than the first one in header file + if ( w[page] > wavelength ){ + memcpy(p, c, L); + return true; + } + + while( w[page] < wavelength ) + { + page++; + //if wavelength is larger than the last wavelength in header file + if (page == B) { + memcpy(p, c + (B - 1) * X(), L); + return true; + } + } + if ( wavelength < w[page] ) + { + p1=(T*)malloc( L ); //memory allocation + p2=(T*)malloc( L ); + + memcpy(p1, c + (page - 1) * X(), L); + memcpy(p2, c + page * X(), L); + + for(unsigned long long i=0; i < X(); i++){ + double r = (double) (wavelength - w[page-1]) / (double) (w[page] - w[page-1]); + p[i] = (T)(((double)p2[i] - (double)p1[i]) * r + (double)p1[i]); + } + } + else //if the wavelength is equal to a wavelength in header file + memcpy(p, c + page * X(), L); + + return true; + } + + /// Retrieve a single spectrum (B-axis line) at a given (x, y) location and stores it in pre-allocated memory. + + /// @param p is a pointer to pre-allocated memory at least B * sizeof(T) in size. + /// @param x is the x-coordinate (dimension 1) of the spectrum. + /// @param y is the y-coordinate (dimension 2) of the spectrum. + bool spectrum(T* p, size_t n, bool PROGRESS = false){ + size_t y = n / X(); + size_t x = n - y * X(); + return binary::read_line_1(p, x, y, PROGRESS); + } + bool spectrum(T * p, unsigned long long x, unsigned long long y, bool PROGRESS = false){ + return binary::read_line_1(p, x, y, PROGRESS); + } + + /// Retrieve a single pixel and stores it in pre-allocated memory. + + /// @param p is a pointer to pre-allocated memory at least sizeof(T) in size. + /// @param n is an integer index to the pixel using linear array indexing. + bool pixel(T * p, unsigned long long n){ + + //calculate the corresponding x, y + unsigned long long x = n % X(); + unsigned long long y = n / X(); + + //get the pixel + return spectrum(p, x, y); + } + + //given a Y ,return a XZ slice + bool read_plane_xz(T * p, size_t y){ + return binary::read_plane_2(p, y); + } + + //given a Y, return ZX slice (transposed such that the spectrum is the leading dimension) + bool read_plane_zx(T* p, size_t y){ + T* temp = (T*) malloc(X() * Z() * sizeof(T)); //allocate space to store the temporary xz plane + if(!binary::read_plane_2(temp, y)) //load the plane from disk + return false; + + size_t z, x; + for(z = 0; z < Z(); z++){ + for(x = 0; x <= z; x++){ + p[x * Z() + z] = temp[z * X() + x]; //copy to the destination frame + } + } + return true; + } + + //load a frame y into a pre-allocated double-precision array + int read_plane_xzd(double* f, size_t y){ + size_t XB = X() * Z(); + T* temp = (T*) malloc(XB * sizeof(T)); //create a temporary location to store the plane at current precision + if(!read_plane_y(temp, y)) return 1; //read the plane in its native format, if it fails return a 1 + for(size_t i = 0; i < XB; i++) f[i] = temp[i]; //convert the plane to a double + return 0; + } + + //given a Y, return ZX slice (transposed such that the spectrum is the leading dimension) + int read_plane_zxd(double* p, size_t y){ + T* temp = (T*) malloc(X() * Z() * sizeof(T)); //allocate space to store the temporary xz plane + binary::read_plane_2(temp, y); //load the plane from disk + size_t z, x; + for(z = 0; z < Z(); z++){ + for(x = 0; x < X(); x++){ + p[x * Z() + z] = (double)temp[z * X() + x]; //copy to the destination frame + } + } + return 0; + } + + + /// Perform baseline correction given a list of baseline points and stores the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param wls is the list of baseline points based on band labels. + bool baseline(std::string outname, std::vector wls, unsigned char* mask = NULL, bool PROGRESS = false){ + + unsigned long long N = wls.size(); //get the number of baseline points + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + //simplify image resolution + unsigned long long ZX = Z() * X(); //calculate the number of points in a Y slice + unsigned long long L = ZX * sizeof(T); //calculate the number of bytes of a Y slice + unsigned long long B = Z(); + + T* c; //pointer to the current Y slice + c = (T*)malloc(L); //memory allocation + + T* a; //pointer to the two YZ lines surrounding the current YZ line + T* b; + + a = (T*)malloc(X() * sizeof(T)); + b = (T*)malloc(X() * sizeof(T)); + + + double ai, bi; //stores the two baseline points wavelength surrounding the current band + double ci; //stores the current band's wavelength + unsigned long long control; + + if (a == NULL || b == NULL || c == NULL){ + std::cout<<"ERROR: error allocating memory"; + exit(1); + } + // loop start correct every y slice + + for (unsigned long long k =0; k < Y(); k++) + { + //get the current y slice + read_plane_xz(c, k); + + //initialize lownum, highnum, low, high + ai = w[0]; + control=0; + //if no baseline point is specified at band 0, + //set the baseline point at band 0 to 0 + if(wls[0] != w[0]){ + bi = wls[control]; + memset(a, (char)0, X() * sizeof(T) ); + } + //else get the low band + else{ + control++; + read_x_from_xz(a, c, ai); + bi = wls[control]; + } + //get the high band + read_x_from_xz(b, c, bi); + + //correct every YZ line + + for(unsigned long long cii = 0; cii < B; cii++){ + + //update baseline points, if necessary + if( w[cii] >= bi && cii != B - 1) { + //if the high band is now on the last BL point + if (control != N-1) { + + control++; //increment the index + + std::swap(a, b); //swap the baseline band pointers + + ai = bi; + bi = wls[control]; + read_x_from_xz(b, c, bi); + + } + //if the last BL point on the last band of the file? + else if ( wls[control] < w[B - 1]) { + + std::swap(a, b); //swap the baseline band pointers + + memset(b, (char)0, X() * sizeof(T) ); //clear the high band + + ai = bi; + bi = w[B - 1]; + } + } + + ci = w[cii]; + + unsigned long long jump = cii * X(); + //perform the baseline correction + for(unsigned i=0; i < X(); i++) + { + double r = (double) (ci - ai) / (double) (bi - ai); + c[i + jump] =(T) ( c[i + jump] - (b[i] - a[i]) * r - a[i] ); + } + + }//loop for YZ line end + target.write(reinterpret_cast(c), L); //write the corrected data into destination + + if(PROGRESS) progress = (double)(k+1) / Y() * 100; + }//loop for Y slice end + + free(a); + free(b); + free(c); + target.close(); + + return true; + + } + + /// Normalize all spectra based on the value of a single band, storing the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param w is the label specifying the band that the hyperspectral image will be normalized to. + /// @param t is a threshold specified such that a spectrum with a value at w less than t is set to zero. Setting this threshold allows the user to limit division by extremely small numbers. + bool ratio(std::string outname, double w, unsigned char* mask = NULL, bool PROGRESS = false) + { + unsigned long long B = Z(); //calculate the number of bands + unsigned long long ZX = Z() * X(); + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + unsigned long long L = ZX * sizeof(T); + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + T * c; //pointer to the current ZX slice + T * b; //pointer to the standard band + + b = (T*)malloc( S ); //memory allocation + c = (T*)malloc( L ); + + band(b, w); //get the certain band into memory + + for(unsigned long long j = 0; j < Y(); j++) + { + read_plane_xz(c, j); + for(unsigned long long i = 0; i < B; i++) + { + for(unsigned long long m = 0; m < X(); m++) + { + if( mask != NULL && !mask[m + j * X()] ) + c[m + i * X()] = (T)0.0; + else + c[m + i * X()] = c[m + i * X()] / b[m + j * X()]; + } + } + target.write(reinterpret_cast(c), L); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / Y() * 100; + } + + free(b); + free(c); + target.close(); + return true; + } + + void normalize(std::string outfile, unsigned char* mask = NULL, bool PROGRESS = false){ + std::cout<<"ERROR: algorithm not implemented"< bandlist, unsigned char* mask = NULL, bool PROGRESS = NULL) { + std::cout << "ERROR: select() not implemented for BIL" << std::endl; + exit(1); + } + + /// Convert the current BIL file to a BSQ file with the specified file name. + + /// @param outname is the name of the output BSQ file to be saved to disk. + bool bsq(std::string outname, bool PROGRESS = false) + { + unsigned long long S = X() * Y() * sizeof(T); //calculate the number of bytes in a band + + std::ofstream target(outname.c_str(), std::ios::binary); + std::string headername = outname + ".hdr"; + + T * p; //pointer to the current band + p = (T*)malloc(S); + + for ( unsigned long long i = 0; i < Z(); i++) + { + band_index(p, i); + target.write(reinterpret_cast(p), S); //write a band data into target file + + if(PROGRESS) progress = (double)(i+1) / Z() * 100; //store the progress for the current operation + } + + free(p); + target.close(); + return true; + } + + /// Convert the current BIL file to a BIP file with the specified file name. + + /// @param outname is the name of the output BIP file to be saved to disk. + bool bip(std::string outname, bool PROGRESS = false) + { + unsigned long long S = X() * Z() * sizeof(T); //calculate the number of bytes in a ZX slice + + std::ofstream target(outname.c_str(), std::ios::binary); + std::string headername = outname + ".hdr"; + + T * p; //pointer to the current XZ slice for bil file + p = (T*)malloc(S); + T * q; //pointer to the current ZX slice for bip file + q = (T*)malloc(S); + + for ( unsigned long long i = 0; i < Y(); i++) + { + read_plane_xz(p, i); + for ( unsigned long long k = 0; k < Z(); k++) + { + unsigned long long ks = k * X(); + for ( unsigned long long j = 0; j < X(); j++) + q[k + j * Z()] = p[ks + j]; + + if(PROGRESS) progress = (double)((i+1) * Z() + k+1) / (Z() * Y()) * 100; //store the progress for the current operation + } + + target.write(reinterpret_cast(q), S); //write a band data into target file + } + + free(p); + free(q); + target.close(); + return true; + } + + + /// Return a baseline corrected band given two adjacent baseline points and their bands. The result is stored in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lp is a pointer to an array holding the band image for the left baseline point + /// @param rp is a pointer to an array holding the band image for the right baseline point + /// @param wavelength is the label value for the requested baseline-corrected band + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size. + bool baseline_band(double lb, double rb, T* lp, T* rp, double wavelength, T* result){ + + unsigned long long XY = X() * Y(); + band(result, wavelength); //get band + + //perform the baseline correction + double r = (double) (wavelength - lb) / (double) (rb - lb); + for(unsigned long long i=0; i < XY; i++){ + result[i] =(T) (result[i] - (rp[i] - lp[i]) * r - lp[i] ); + } + return true; + } + + /// Return a baseline corrected band given two adjacent baseline points. The result is stored in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param bandwavelength is the label value for the desired baseline-corrected band + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size. + bool height(double lb, double rb, double bandwavelength, T* result){ + + T* lp; + T* rp; + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + + band(lp, lb); + band(rp, rb); + + baseline_band(lb, rb, lp, rp, bandwavelength, result); + + free(lp); + free(rp); + return true; + } + + + + /// Calculate the area under the spectrum between two specified points and stores the result in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label value for the left bound (start of the integration) + /// @param rab is the label value for the right bound (end of the integration) + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool area(double lb, double rb, double lab, double rab, T* result){ + + T* lp; //left band pointer + T* rp; //right band pointer + T* cur; //current band 1 + T* cur2; //current band 2 + + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + cur = (T*) malloc(S); + cur2 = (T*) malloc(S); + + memset(result, (char)0, S); + + //find the wavelenght position in the whole band + unsigned long long n = w.size(); + unsigned long long ai = 0; //left bound position + unsigned long long bi = n - 1; //right bound position + + + + //to make sure the left and the right bound are in the bandwidth + if (lb < w[0] || rb < w[0] || lb > w[n-1] || rb >w[n-1]){ + std::cout<<"ERROR: left bound or right bound out of bandwidth"< rb){ + std::cout<<"ERROR: right bound should be bigger than left bound"<= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); + band(rp, rb); + + //calculate the beginning and the ending part + baseline_band(lb, rb, lp, rp, rab, cur2); //ending part + baseline_band(lb, rb, lp, rp, w[bi], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + + //calculate the area + ai++; + for(unsigned long long i = ai; i <= bi ;i++) + { + baseline_band(lb, rb, lp, rp, w[ai], cur2); + for(unsigned long long j = 0; j < XY; j++) + { + result[j] += (T)((w[ai] - w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the ratio of two baseline-corrected peaks. The result is stored in a pre-allocated array. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool ph_to_ph(T* result, double lb1, double rb1, double pos1, double lb2, double rb2, double pos2, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the two peak band + height(lb1, rb1, pos1, p1); + height(lb2, rb2, pos2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between a peak area and peak height. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_ph(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double pos, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + height(lb2, rb2, pos, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between two peak areas. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param lab1 is the label value for the left bound (start of the integration) of the first peak (numerator) + /// @param rab1 is the label value for the right bound (end of the integration) of the first peak (numerator) + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param lab2 is the label value for the left bound (start of the integration) of the second peak (denominator) + /// @param rab2 is the label value for the right bound (end of the integration) of the second peak (denominator) + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_pa(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double lab2, double rab2, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + area(lb2, rb2, lab2, rab2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the definite integral of a baseline corrected peak. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the definite integral + /// @param rab is the label for the end of the definite integral + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool x_area(double lb, double rb, double lab, double rab, T* result){ + T* lp; //left band pointer + T* rp; //right band pointer + T* cur; //current band 1 + T* cur2; //current band 2 + + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + cur = (T*) malloc(S); + cur2 = (T*) malloc(S); + + memset(result, (char)0, S); + + //find the wavelenght position in the whole band + unsigned long long n = w.size(); + unsigned long long ai = 0; //left bound position + unsigned long long bi = n - 1; //right bound position + + //to make sure the left and the right bound are in the bandwidth + if (lb < w[0] || rb < w[0] || lb > w[n-1] || rb >w[n-1]){ + std::cout<<"ERROR: left bound or right bound out of bandwidth"< rb){ + std::cout<<"ERROR: right bound should be bigger than left bound"<= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); + band(rp, rb); + + //calculate the beginning and the ending part + baseline_band(lb, rb, lp, rp, rab, cur2); //ending part + baseline_band(lb, rb, lp, rp, w[bi], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * (rab + w[bi]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * (w[ai] + lab) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + + //calculate f(x) times x + ai++; + for(unsigned long long i = ai; i <= bi ;i++) + { + baseline_band(lb, rb, lp, rp, w[ai], cur2); + for(unsigned long long j = 0; j < XY; j++) + { + result[j] += (T)((w[ai] - w[ai-1]) * (w[ai] + w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the centroid of a baseline corrected peak. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the peak + /// @param rab is the label for the end of the peak + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool centroid(T* result, double lb, double rb, double lab, double rab, unsigned char* mask = NULL){ + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + x_area(lb, rb, lab, rab, p1); + area(lb, rb, lab, rab, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]) + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Create a mask based on a given band and threshold value. + + /// All pixels in the + /// specified band greater than the threshold are true and all pixels less than the threshold are false. + /// @param mask_band is the band used to specify the mask + /// @param threshold is the threshold used to determine if the mask value is true or false + /// @param p is a pointer to a pre-allocated array at least X * Y in size + bool build_mask(unsigned char* out_mask, double mask_band, double lower, double upper, unsigned char* mask = NULL, bool PROGRESS = false){ + + T* temp = (T*)malloc(X() * Y() * sizeof(T)); //allocate memory for the certain band + band(temp, mask_band, PROGRESS); + + for (unsigned long long i = 0; i < X() * Y(); i++) { + if(mask == NULL || mask[i] != 0){ + if(temp[i] > lower && temp[i] < upper){ + out_mask[i] = 255; + } + else + out_mask[i] = 0; + } + } + + free(temp); + return true; + } + + /// Apply a mask file to the BSQ image, setting all values outside the mask to zero. + + /// @param outfile is the name of the masked output file + /// @param p is a pointer to memory of size X * Y, where p(i) = 0 for pixels that will be set to zero. + bool apply_mask(std::string outfile, unsigned char* p, bool PROGRESS = false){ + + std::ofstream target(outfile.c_str(), std::ios::binary); + + //I THINK THIS IS WRONG + unsigned long long XZ = X() * Z(); //calculate the number of values in a page on disk + unsigned long long L = XZ * sizeof(T); //calculate the size of the page (in bytes) + + T * temp = (T*)malloc(L); //allocate memory for a temporary page + + for (unsigned long long i = 0; i < Y(); i++) //for each value in Y() (BIP should be X) + { + read_plane_xz(temp, i); //retrieve an ZX slice, stored in temp + for ( unsigned long long j = 0; j < Z(); j++) //for each Z() (Y) + { + for (unsigned long long k = 0; k < X(); k++) //for each band + { + if(p[i * X() + k] == 0) + temp[j * X() + k] = 0; + else + continue; + } + } + target.write(reinterpret_cast(temp), L); //write a band data into target file + if(PROGRESS) progress = (double)(i+1) / (double)Y() * 100; + } + target.close(); + free(temp); + return true; + } + + /// Copies all spectra corresponding to nonzero values of a mask into a pre-allocated matrix of size (B x P) + /// where P is the number of masked pixels and B is the number of bands. The allocated memory can be accessed + /// using the following indexing: i = p*B + b + /// @param matrix is the destination for the pixel data + /// @param mask is the mask + bool sift(T* matrix, unsigned char* mask = NULL, bool PROGRESS = false){ + size_t Lbytes = sizeof(T) * X(); + T* line = (T*) malloc( Lbytes ); //allocate space for a line + + file.seekg(0, std::ios::beg); //seek to the beginning of the file + + size_t pl; + size_t p = 0; //create counter variables + for(size_t y = 0; y < Y(); y++){ //for each line in the data set + for(size_t b = 0; b < Z(); b++){ //for each band in the data set + pl = 0; //initialize the pixel offset for the current line to zero (0) + file.read( (char*)line, Lbytes ); //read the current line + for(size_t x = 0; x < X(); x++){ + if(mask == NULL || mask[y * X() + x]){ //if the current pixel is masked + size_t i = (p + pl) * Z() + b; //calculate the index in the sifted matrix + matrix[i] = line[x]; //store the current value in the line at the correct matrix location + pl++; //increment the pixel pointer + } + } + if(PROGRESS) progress = (double)( (y+1)*Z() + 1) / (double)(Y() * Z()) * 100; + } + p += pl; //add the line increment to the running pixel index + } + return true; + } + + /// Saves to disk only those spectra corresponding to mask values != 0 + /// Unlike the BIP and BSQ versions of this function, this version saves a different format: the BIL file is saved as a BIP + bool sift(std::string outfile, unsigned char* p, bool PROGRESS = false){ + // Assume X() = X, Y() = Y, Z() = Z. + std::ofstream target(outfile.c_str(), std::ios::binary); + + //for loading pages: + unsigned long long XZ = X() * Z(); //calculate the number of values in an XZ page on disk + unsigned long long B = Z(); //calculate the number of bands + unsigned long long L = XZ * sizeof(T); //calculate the size of the page (in bytes) + + //allocate temporary memory for a XZ slice + T* slice = (T*) malloc(L); + + //allocate temporary memory for a spectrum + T* spec = (T*) malloc(B * sizeof(T)); + + //for each slice along the y axis + for (unsigned long long y = 0; y < Y(); y++) //Select a page by choosing Y coordinate, Y() + { + read_plane_xz(slice, y); //retrieve an ZX page, store in "slice" + + //for each sample along X + for (unsigned long long x = 0; x < X(); x++) //Select a pixel by choosing X coordinate in the page, X() + { + //if the mask != 0 at that xy pixel + if (p[y * X() + x] != 0) //if the mask != 0 at that XY pixel + { + //for each band at that pixel + for (unsigned long long b = 0; b < B; b++) //Select a voxel by choosing Z coordinate at the pixel + { + spec[b] = slice[b*X() + x]; //Pass the correct spectral value from XZ page into the spectrum to be saved. + } + target.write((char*)spec, B * sizeof(T)); //write that spectrum to disk. Size is L2. + } + + if(PROGRESS) progress = (double) ((y+1) * X() + x+1) / (Y() * X()) * 100; + } + } + target.close(); + free(slice); + free(spec); + + return true; + } + + /// Calculate the mean band value (average along B) at each pixel location. + + /// @param p is a pointer to memory of size X * Y * sizeof(T) that will store the band averages. + bool band_avg(T* p){ + unsigned long long XZ = X() * Z(); + T* temp = (T*)malloc(sizeof(T) * XZ); + T* line = (T*)malloc(sizeof(T) * X()); + + for (unsigned long long i = 0; i < Y(); i++){ + getY(temp, i); + //initialize x-line + for (unsigned long long j = 0; j < X(); j++){ + line[j] = 0; + } + unsigned long long c = 0; + for (unsigned long long j = 0; j < Z(); j++){ + for (unsigned long long k = 0; k < X(); k++){ + line[k] += temp[c] / (T)Z(); + c++; + } + } + for (unsigned long long j = 0; j < X(); j++){ + p[j + i * X()] = line[j]; + } + } + free(temp); + return true; + } + + /// Calculate the mean value for all masked (or valid) pixels in a band and returns the average spectrum + + /// @param p is a pointer to pre-allocated memory of size [B * sizeof(T)] that stores the mean spectrum + /// @param mask is a pointer to memory of size [X * Y] that stores the mask value at each pixel location + bool mean_spectrum(double* m, double* std, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long XZ = X() * Z(); + unsigned long long XY = X() * Y(); + T* temp = (T*)malloc(sizeof(T) * XZ); + memset(m, 0, Z() * sizeof(double)); //initialize the mean to zero + double* e_x2 = (double*)malloc(Z() * sizeof(double)); //allocate space for E[x^2] + memset(e_x2, 0, Z() * sizeof(double)); //initialize E[x^2] to zero + //calculate vaild number in a band + size_t count = nnz(mask); //count the number of pixels in the mask + + double x; //create a register to store the pixel value + for (unsigned long long k = 0; k < Y(); k++){ + read_plane_xz(temp, k); + unsigned long long kx = k * X(); + for (unsigned long long i = 0; i < X(); i++){ + if (mask == NULL || mask[kx + i] != 0){ + for (unsigned long long j = 0; j < Z(); j++){ + x = temp[j * X() + i]; + m[j] += x / (double)count; + e_x2[j] += x*x / (double)count; + } + } + } + if(PROGRESS) progress = (double)(k+1) / Y() * 100; + } + + for(size_t i = 0; i < Z(); i++) //calculate the standard deviation + std[i] = sqrt(e_x2[i] - m[i] * m[i]); + + free(temp); + return true; + } + + int co_matrix_cublas(double* co, double* avg, unsigned char *mask, bool PROGRESS = false){ + cublasStatus_t stat; + cublasHandle_t handle; + + progress = 0; //initialize the progress to zero (0) + size_t XY = X() * Y(); //calculate the number of elements in a band image + size_t XB = X() * Z(); + size_t B = Z(); //calculate the number of spectral elements + + double* F = (double*)malloc(sizeof(double) * B * X()); //allocate space for the frame that will be pulled from the file + double* F_dev; + HANDLE_ERROR(cudaMalloc(&F_dev, X() * B * sizeof(double))); //allocate space for the frame on the GPU + double* s_dev; //declare a device pointer that will store the spectrum on the GPU + double* A_dev; //declare a device pointer that will store the covariance matrix on the GPU + double* avg_dev; //declare a device pointer that will store the average spectrum + HANDLE_ERROR(cudaMalloc(&s_dev, B * sizeof(double))); //allocate space on the CUDA device for a spectrum + HANDLE_ERROR(cudaMalloc(&A_dev, B * B * sizeof(double))); //allocate space on the CUDA device for the covariance matrix + HANDLE_ERROR(cudaMemset(A_dev, 0, B * B * sizeof(double))); //initialize the covariance matrix to zero (0) + HANDLE_ERROR(cudaMalloc(&avg_dev, XB * sizeof(double))); //allocate space on the CUDA device for the average spectrum + for(size_t x = 0; x < X(); x++) //make multiple copies of the average spectrum in order to build a matrix + HANDLE_ERROR(cudaMemcpy(&avg_dev[x * B], avg, B * sizeof(double), cudaMemcpyHostToDevice)); + //stat = cublasSetVector((int)B, sizeof(double), avg, 1, avg_dev, 1); //copy the average spectrum to the CUDA device + + double ger_alpha = 1.0/(double)XY; //scale the outer product by the inverse of the number of samples (mean outer product) + double axpy_alpha = -1; //multiplication factor for the average spectrum (in order to perform a subtraction) + + CUBLAS_HANDLE_ERROR(stat = cublasCreate(&handle)); //create a cuBLAS instance + if (stat != CUBLAS_STATUS_SUCCESS) return 1; //test the cuBLAS instance to make sure it is valid + + else std::cout<<"Using cuBLAS to calculate the mean covariance matrix..."<= 0) { //if a CUDA device is specified + int dev_count; + HANDLE_ERROR(cudaGetDeviceCount(&dev_count)); //get the number of CUDA devices + std::cout << "Number of CUDA devices: " << dev_count << std::endl; //output the number of CUDA devices + cudaDeviceProp prop; + //int best_device_id = 0; //stores the best CUDA device + //float best_device_cc = 0.0f; //stores the compute capability of the best device + std::cout << "CUDA devices----" << std::endl; + for (int d = 0; d < dev_count; d++) { //for each CUDA device + cudaGetDeviceProperties(&prop, d); //get the property of the first device + //float cc = prop.major + prop.minor / 10.0f; //calculate the compute capability + std::cout << d << ": [" << prop.major << "." << prop.minor << "] " << prop.name << std::endl; //display the device information + //if(cc > best_device_cc){ + // best_device_cc = cc; //if this is better than the previous device, use it + // best_device_id = d; + //} + } + if (dev_count > 0 && dev_count > cuda_device) { //if the first device is not an emulator + cudaGetDeviceProperties(&prop, cuda_device); //get the property of the requested CUDA device + if (prop.major != 9999) { + std::cout << "Using device " << cuda_device << std::endl; + HANDLE_ERROR(cudaSetDevice(cuda_device)); + int status = co_matrix_cublas(co, avg, mask, PROGRESS); //use cuBLAS to calculate the covariance matrix + if (status == 0) return true; //if the cuBLAS function returned correctly, we're done + } + } + } + + //memory allocation + unsigned long long xy = X() * Y(); + unsigned long long B = Z(); + T* temp = (T*)malloc(sizeof(T) * B); + //count vaild pixels in a band + unsigned long long count = 0; + for (unsigned long long j = 0; j < xy; j++){ + if (mask == NULL || mask[j] != 0){ + count++; + } + } + //initialize correlation matrix + for (unsigned long long i = 0; i < B; i++){ + for (unsigned long long k = 0; k < B; k++){ + co[i * B + k] = 0; + } + } + //calculate correlation coefficient matrix + for (unsigned long long j = 0; j < xy; j++){ + if (mask == NULL || mask[j] != 0){ + pixel(temp, j); + for (unsigned long long i = 0; i < B; i++){ + for (unsigned long long k = i; k < B; k++){ + co[i * B + k] += ((double)temp[i] - (double)avg[i]) * ((double)temp[k] - (double)avg[k]) / (double)count; + } + } + } + if(PROGRESS) progress = (double)(j+1) / xy * 100; + } + //because correlation matrix is symmetric + for (unsigned long long i = 0; i < B; i++){ + for (unsigned long long k = i + 1; k < B; k++){ + co[k * B + i] = co[i * B + k]; + } + } + + free(temp); + return true; + } + + + /// Crop a region of the image and save it to a new file. + + /// @param outfile is the file name for the new cropped image + /// @param x0 is the lower-left x pixel coordinate to be included in the cropped image + /// @param y0 is the lower-left y pixel coordinate to be included in the cropped image + /// @param x1 is the upper-right x pixel coordinate to be included in the cropped image + /// @param y1 is the upper-right y pixel coordinate to be included in the cropped image + bool crop(std::string outfile, unsigned long long x0, + unsigned long long y0, + unsigned long long x1, + unsigned long long y1, + unsigned long long b0, + unsigned long long b1, + bool PROGRESS = false){ + + //calculate the new image parameters + unsigned long long samples = x1 - x0 + 1; + unsigned long long lines = y1 - y0 + 1; + unsigned long long bands = b1 - b0 + 1; + + //calculate the size of a line + unsigned long long L = samples * sizeof(T); + + + //allocate space for a line + T* temp = (T*)malloc(bands * L); + + //create an output stream to store the cropped file + std::ofstream out(outfile.c_str(), std::ios::binary); + + //calculate the distance between bands + unsigned long long jumpb = (X() - samples) * sizeof(T); + + //distance needed to jump from the previous line of the last band to the next line of the first band + unsigned long long longjump = ((Z() - bands) * X()) * sizeof(T); + + //set the start position for the cropped region + file.seekg((y0 * X() * Z() + b0 * X() + x0) * sizeof(T), std::ios::beg); + + for (unsigned long long x = 0; x < lines; x++) + { + for (unsigned long long z = b0; z <= b1; z++) + { + file.read((char *)(temp + z * samples), sizeof(T) * samples); + file.seekg(jumpb, std::ios::cur); //go to the next band + + if(PROGRESS) progress = (double)(x * (b1 - b0 + 1) + z + 1) / (lines * (b1 - b0 + 1)) * 100; + } + + //write slice data into target file + out.write(reinterpret_cast(temp), bands * L); + + //seek to the beginning of the next X-Z slice + file.seekg(longjump, std::ios::cur); + } + + //free the temporary frame + free(temp); + + return true; + } + + /// Remove a list of bands from the ENVI file + + /// @param outfile is the file name for the output hyperspectral image (with trimmed bands) + /// @param b is an array of bands to be eliminated + void trim(std::string outfile, std::vector band_array, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of the input file + + size_t Xb = X() * sizeof(T); //calculate the number of bytes in a line + T* line = (T*)malloc(Xb); //allocate space for a line + + size_t i; //create an index into the band array + for(size_t y = 0; y < Y(); y++){ //for each Y plane + i = 0; + for(size_t b = 0; b < Z(); b++){ //for each band + if(b != band_array[i]){ //if this band isn't trimmed + file.read((char*)line, Xb); //read a line + out.write((char*)line, Xb); //write the line + } + else{ + file.seekg(Xb, std::ios::cur); //if this band is trimmed, skip it + i++; + } + } + if(PROGRESS) progress = (double)(y+1) / (double)Y() * 100; + } + free(line); + } + + /// Combine two BSQ images along the Y axis + + /// @param outfile is the combined file to be output + /// @param infile is the input file stream for the image to combine with this one + /// @param Sx is the size of the second image along X + /// @param Sy is the size of the second image along Y + /// @param offset is a shift (negative or positive) in the combined image to the left or right + void combine(std::string outfile, bil* C, long long xp, long long yp, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + + size_t S[2]; //size of the output band image + size_t p0[2]; //position of the current image in the output + size_t p1[2]; //position of the source image in the output + + hsi::calc_combined_size(xp, yp, C->X(), C->Y(), S[0], S[1], p0[0], p0[1], p1[0], p1[1]); //calculate the image placement parameters + + size_t line_bytes = X() * sizeof(T); + size_t band_bytes = X() * Y() * sizeof(T); + T* cur = (T*)malloc(line_bytes); //allocate space for a band of the current image + + size_t line_src_bytes = C->X() * sizeof(T); + size_t band_src_bytes = C->X() * C->Y() * sizeof(T); + T* src = (T*)malloc(line_src_bytes); //allocate space for a band of the source image + + size_t line_dst_bytes = S[0] * sizeof(T); + size_t band_dst_bytes = S[0] * S[1] * sizeof(T); + T* dst = (T*)malloc(line_dst_bytes); //allocate space for a band of the destination image + + for(size_t y = 0; y < S[1]; y++){ //for each line in the destination file + memset(dst, 0, line_dst_bytes); //set all values to zero (0) in the destination image + for(size_t b = 0; b < Z(); b++){ //for each band in both images + if(y >= p0[1] && y < p0[1] + Y()){ //if the current image crosses this line + file.read((char*)cur, line_bytes); //read a line from the current image + memcpy(&dst[p0[0]], cur, line_bytes); //copy the current line to the correct spot in the destination line + } + if(y >= p1[1] && y < p1[1] + C->Y()){ //if the source image crosses this line + C->file.read((char*)src, line_src_bytes); //read a line from the source image + memcpy(&dst[p1[0]], src, line_src_bytes); //copy the source line into the correct spot in the destination line + } + out.write((char*)dst, line_dst_bytes); + if(PROGRESS) progress = (double)((y + 1)*Z() + b + 1) / (double) (Z() * S[1]) * 100; + } + } + } + + ///Append two files together along the band dimension + void append(std::string outfile, bil* C, bool PROGRESS = false) { + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + size_t a_bytes = X() * Z() * sizeof(T); //calculate the number of bytes in a single plane of this file + size_t b_bytes = C->X() * C->Z() * sizeof(T); //calculate the number of bytes in a single plane of the appending file + T* a = (T*)malloc(a_bytes); //allocate space for a plane of the current file + T* b = (T*)malloc(b_bytes); //allocate space for a plane of the appended file + if (PROGRESS) progress = 0; + for (size_t y = 0; y < Y(); y++) { + read_plane_xz(a, y); //read a plane from the current file + out.write((char*)a, a_bytes); //write the plane to disk + C->read_plane_xz(b, y); //read a plane from the appending file + out.write((char*)b, b_bytes); + if (PROGRESS) progress = (double)(y + 1) / (double)(Y()) * 100; + } + + out.close(); + } + + /// Convolve the given band range with a kernel specified by a vector of coefficients. + + /// @param outfile is an already open stream to the output file + /// @param C is an array of coefficients + /// @param start is the band to start processing (the first coefficient starts here) + /// @param nbands is the number of bands to process + /// @param center is the index for the center coefficient for the kernel (used to set the wavelengths in the output file) + + void convolve(std::string outfile, std::vector C, size_t start, size_t end, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + size_t B = end - start + 1; + size_t Xb = X() * sizeof(T); //calculate the size of a band (frame) in bytes + size_t XBb = Xb * Z(); + + size_t N = C.size(); //get the number of bands that the kernel spans + std::deque frame(N, NULL); //create an array to store pointers to each frame + for(size_t f = 0; f < N; f++) + frame[f] = (T*)malloc(Xb); //allocate space for the frame + + T* outline = (T*)malloc(Xb); //allocate space for the output band + + //Implementation: In order to minimize reads from secondary storage, each band is only loaded once into the 'frame' deque. + // When a new band is loaded, the last band is popped, a new frame is copied to the pointer, and it is + // re-inserted into the deque. + for(size_t y = 0; y < Y(); y++){ + file.seekg(y * XBb + start * Xb, std::ios::beg); //move to the beginning of the 'start' band + for(size_t f = 0; f < N; f++) //for each frame + file.read((char*)frame[f], Xb); //load the frame + for(size_t b = 0; b < B; b++){ //for each band + memset(outline, 0, Xb); //set the output band to zero (0) + for(size_t c = 0; c < N; c++){ //for each frame (corresponding to each coefficient) + for(size_t x = 0; x < X(); x++){ //for each pixel + if(mask == NULL || mask[y * X() + x]){ + outline[x] += (T)(C[c] * frame[c][x]); //calculate the contribution of the current frame (scaled by the corresponding coefficient) + } + } + } + out.write((char*)outline, Xb); //output the band + file.read((char*)frame[0], Xb); //read the next band + frame.push_back(frame.front()); //put the first element in the back + frame.pop_front(); //pop the first element + } + if(PROGRESS) progress = (double)(y+1) / (double)Y() * 100; + } + } + + /// Approximate the spectral derivative of the image + void deriv(std::string outfile, size_t d, size_t order, const std::vector w = std::vector(), unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + + size_t Xb = X() * sizeof(T); //calculate the size of a line (frame) in bytes + size_t XBb = Xb * Z(); + size_t B = Z(); + + file.seekg(0, std::ios::beg); //seek to the beginning of the file + + size_t N = order + d; //approximating a derivative requires order + d samples + std::deque frame(N, NULL); //create an array to store pointers to each frame + for(size_t f = 0; f < N; f++) //for each frame + frame[f] = (T*)malloc(Xb); //allocate space for the frame + + T* outline = (T*)malloc(Xb); //allocate space for the output band + + //Implementation: In order to minimize reads from secondary storage, each band is only loaded once into the 'frame' deque. + // When a new band is loaded, the last band is popped, a new frame is copied to the pointer, and it is + // re-inserted into the deque. + size_t mid = (size_t)(N / 2); //calculate the mid point of the kernel + size_t iw; //index to the first wavelength used to evaluate the derivative at this band + + for(size_t y = 0; y < Y(); y++){ + //file.seekg(y * XBb, std::ios::beg); //seek to the beginning of the current Y plane + for(size_t f = 0; f < N; f++) //for each frame + file.read((char*)frame[f], Xb); //load a line + + for(size_t b = 0; b < B; b++){ //for each band + if(b < mid) //calculate the first wavelength used to evaluate the derivative at this band + iw = 0; + else if(b > B - (N - mid + 1)) + iw = B - N; + else{ + iw = b - mid; + file.read((char*)frame[0], Xb); //read the next band + frame.push_back(frame.front()); //put the first element in the back + frame.pop_front(); //pop the first element + } + std::vector w_pts(w.begin() + iw, w.begin() + iw + N); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[b], w_pts, d); //get the optimal sample weights + + memset(outline, 0, Xb); //set the output band to zero (0) + for(size_t c = 0; c < N; c++){ //for each frame (corresponding to each coefficient) + for(size_t x = 0; x < X(); x++){ //for each pixel + if(mask == NULL || mask[y * X() + x]){ + outline[x] += (T)(C[c] * frame[c][x]); //calculate the contribution of the current frame (scaled by the corresponding coefficient) + } + } + } + out.write((char*)outline, Xb); //output the band + + } + if(PROGRESS) progress = (double)(y+1) / (double)Y() * 100; + } + + } + + bool multiply(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long B = Z(); //calculate the number of bands + unsigned long long ZX = Z() * X(); + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + unsigned long long L = ZX * sizeof(T); + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + + T * c; //pointer to the current ZX slice + c = (T*)malloc( L ); //allocate space for the slice + + for(unsigned long long j = 0; j < Y(); j++){ //for each line + read_plane_xz(c, j); //load the line into memory + for(unsigned long long i = 0; i < B; i++){ //for each band + for(unsigned long long m = 0; m < X(); m++){ //for each sample + if( mask == NULL && mask[m + j * X()] ) //if the pixel is masked + c[m + i * X()] *= (T)v; + } + } + target.write(reinterpret_cast(c), L); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / Y() * 100; //update the progress + } + + free(c); //free the slice memory + target.close(); //close the output file + return true; + } + + bool add(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long B = Z(); //calculate the number of bands + unsigned long long ZX = Z() * X(); + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + unsigned long long L = ZX * sizeof(T); + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + + T * c; //pointer to the current ZX slice + c = (T*)malloc( L ); //allocate space for the slice + + for(unsigned long long j = 0; j < Y(); j++){ //for each line + read_plane_xz(c, j); //load the line into memory + for(unsigned long long i = 0; i < B; i++){ //for each band + for(unsigned long long m = 0; m < X(); m++){ //for each sample + if( mask == NULL && mask[m + j * X()] ) //if the pixel is masked + c[m + i * X()] += (T)v; + } + } + target.write(reinterpret_cast(c), L); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / Y() * 100; //update the progress + } + + free(c); //free the slice memory + target.close(); //close the output file + return true; + } + + /// Close the file. + bool close(){ + file.close(); + return true; + } + + }; +} + +#endif diff --git a/tira/envi/binary.h b/tira/envi/binary.h new file mode 100644 index 0000000..3513e0b --- /dev/null +++ b/tira/envi/binary.h @@ -0,0 +1,655 @@ + +//make sure that this header file is only loaded once +#ifndef RTS_BINARY_H +#define RTS_BINARY_H + +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef USE_CUDA +//CUDA externs +void gpu_permute(char* dest, char* src, size_t sx, size_t sy, size_t sz, size_t d0, size_t d1, size_t d2, size_t typesize); +#include +#endif + +namespace stim{ + +/// This class calculates the optimal setting for independent parameter b (batch size) for +/// minimizing the dependent parameter bps (bytes per second) +class stream_optimizer{ +protected: + size_t Bps[2]; //bytes per second for the previous batch + size_t interval_B; //number of bytes processed this interval + size_t interval_ms; //number of milliseconds spent in the current interval + size_t n[2]; //current batch size (in bytes) + size_t h; //spacing used for finite difference calculations + size_t dn; //delta value (in bytes) for setting the batch size (minimum change in batch parameter) + size_t maxn; //maximum value for the batch size + + double alpha; //alpha value controls the factor of the gradient that is used to calculate the next point (speed of convergence) + + bool sample_step; //calculating the derivative (this class alternates between calculating dBps and B) + bool forward_diff; //evaluate the derivative using forward differences + + size_t window_ms; //size of the interval (in milliseconds) integrated to get a reliable bps value + + // This function rounds x to the nearest value within dB + size_t round_limit(double n0){ + if(n0 < 0) return dn; //if n0 is less than zero, return the lowest possible n + + size_t new_n = (size_t)(n0 + 0.5); //now n0 must be positive, so round it to the nearest integer + if(new_n > maxn) new_n = maxn; //limit the returned size of x to within the specified bounds + + size_t lowest = new_n / dn; + size_t highest = lowest + dn; + size_t diff[2] = {new_n - lowest, highest - new_n}; //calculate the two differences + if(diff[0] < diff[1]) + return lowest; + return highest; + } + +public: + + //constructor initializes a stream optimizer + stream_optimizer(size_t min_batch_size, size_t max_batch_size, double a = 0.003, size_t probe_step = 5, size_t window = 2000){ + //Bps = 0; //initialize to zero bytes per second processed + Bps[0] = Bps[1] = 0; //initialize the bits per second to 0 + interval_B = 0; //zero bytes have been processed at initialization + interval_ms = 0; //no time has been spent on the batch so far + dn = min_batch_size; //set the minimum batch size as the minimum change in batch size + maxn = max_batch_size; //set the maximum batch size + n[0] = max_batch_size; //set B + h = (max_batch_size / min_batch_size) / probe_step * dn; + std::cout<<"h = "< r, unsigned long long h = 0, stim::iotype io = stim::io_in){ + + for(unsigned long long i = 0; i < D; i++) //set the dimensions of the binary file object + R[i] = r[i]; + + header = h; //save the header size + + if(!open_file(filename), io) return false; //open the binary file + + //reset(); + + return test_file_size(); + } + + bool is_open() { + return file.is_open(); + } + + /// Creates a new binary file for streaming + + /// @param filename is the name of the binary file to be created + /// @param r is a STIM vector specifying the size of the file along each dimension + /// @offset specifies how many bytes to offset the file (used to leave room for a header) + bool create(std::string filename, vec r, unsigned long long offset = 0){ + + std::ofstream target(filename.c_str(), std::ios::binary); + + //initialize binary file + T p = 0; + for(unsigned long long i =0; i < r[0] * r[1] * r[2]; i++){ + target.write((char*)(&p), sizeof(T)); + } + + for(unsigned long long i = 0; i < D; i++) //set the dimensions of the binary file object + R[i] = r[i]; + + header = offset; //save the header size + + if(!open_file(filename)) return false; //open the binary file + + return test_file_size(); + } + + /// Writes a single page of data to disk. A page consists of a sequence of data of size R[0] * R[1] * ... * R[D-1]. + + /// @param p is a pointer to the data to be written + /// @param page is the page number (index of the highest-numbered dimension) + bool write_page( T * p, unsigned long long page){ + + if(p == NULL){ + std::cout<<"ERROR: unable to write into file, empty pointer"<= R[2]){ //make sure the bank number is right + std::cout<<"ERROR: page out of range"< R[0] * R[1]){ //make sure the sample and line number is right + std::cout<<"ERROR: sample or line out of range in "<<__FILE__<<" (line "<<__LINE__<<")"<= R[0] || y >= R[1]){ //make sure the sample and line number is right + std::cout<<"ERROR: sample or line out of range"<= R[1] || z >= R[2] ){ + std::cout<<"ERROR: sample ("<= R[0] || z >= R[2] ){ + std::cout<<"ERROR: sample or line out of range in "<<__FILE__<<" (line "<<__LINE__<<")"<= R[0]){ //make sure the number is within the possible range + std::cout<<"ERROR: sample or line out of range in "<<__FILE__<<" (line "<<__LINE__<<")"<= R[1]){ //make sure the bank number is right + std::cout<<"ERROR read_plane_1: page out of range"<= R[0] * R[1] * R[2]){ + std::cout<<"ERROR read_pixel: n is out of range"<= R[0] || y < 0 || y >= R[1] || z < 0 || z > R[2]){ + std::cout<<"ERROR read_pixel: (x,y,z) is out of range"<(t1-t0).count(); + } + + // permutes a block of data from the current interleave to the interleave specified (re-arranged dimensions to the order specified by [d0, d1, d2]) + + size_t permute(T* dest, T* src, size_t sx, size_t sy, size_t sz, size_t d0, size_t d1, size_t d2){ + std::chrono::high_resolution_clock::time_point t0, t1; + t0 = std::chrono::high_resolution_clock::now(); + +#ifdef USE_CUDA + T* gpu_src; + HANDLE_ERROR( cudaMalloc(&gpu_src, sx*sy*sz*sizeof(T)) ); + HANDLE_ERROR( cudaMemcpy(gpu_src, src, sx*sy*sz*sizeof(T), cudaMemcpyHostToDevice) ); + T* gpu_dest; + HANDLE_ERROR( cudaMalloc(&gpu_dest, sx*sy*sz*sizeof(T)) ); + gpu_permute((char*)gpu_dest, (char*)gpu_src, sx, sy, sz, d0, d1, d2, sizeof(T)); + HANDLE_ERROR( cudaMemcpy(dest, gpu_dest, sx*sy*sz*sizeof(T), cudaMemcpyDeviceToHost) ); + HANDLE_ERROR( cudaFree(gpu_src) ); + HANDLE_ERROR( cudaFree(gpu_dest) ); + t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(t1-t0).count(); + +#endif + + size_t d[3] = {d0, d1, d2}; + size_t s[3] = {sx, sy, sz}; + size_t p[3]; + + if(d[0] == 0 && d[1] == 1 && d[2] == 2){ + //this isn't actually a permute - just copy the data + memcpy(dest, src, sizeof(T) * sx * sy * sz); + } + else if(d[0] == 0){ //the individual lines are contiguous, so you can memcpy line-by-line + size_t y, z; + size_t src_idx, dest_idx; + size_t x_bytes = sizeof(T) * sx; + for(z = 0; z < sz; z++){ + p[2] = z; + for(y = 0; y < sy; y++){ + p[1] = y; + src_idx = z * sx * sy + y * sx; + dest_idx = p[d[2]] * s[d[0]] * s[d[1]] + p[d[1]] * s[d[0]]; + memcpy(dest + dest_idx, src + src_idx, x_bytes); + } + } + } + else{ //loop through every damn point + size_t x, y, z; + size_t src_idx, dest_idx; + size_t src_z, src_y; + for(z = 0; z < sz; z++){ + p[2] = z; + src_z = z * sx * sy; + for(y = 0; y < sy; y++){ + p[1] = y; + src_y = src_z + y * sx; + for(x = 0; x < sx; x++){ + p[0] = x; + src_idx = src_y + x; + dest_idx = p[d[2]] * s[d[0]] * s[d[1]] + p[d[1]] * s[d[0]] + p[d[0]]; + dest[dest_idx] = src[src_idx]; + } + } + } + } + t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(t1-t0).count(); + } + +}; + +} + +#endif diff --git a/tira/envi/bip.h b/tira/envi/bip.h new file mode 100644 index 0000000..3af4ea9 --- /dev/null +++ b/tira/envi/bip.h @@ -0,0 +1,1909 @@ +#ifndef STIM_BIP_H +#define STIM_BIP_H + +#include "../envi/envi_header.h" +#include "../envi/bil.h" +#include "../envi/hsi.h" +#include +#include +#include +#include + +//CUDA +//#ifdef CUDA_FOUND +#include +#include +#include "cublas_v2.h" +#include "cufft.h" +//#endif + +namespace stim{ + +/** + The BIP class represents a 3-dimensional binary file stored using band interleaved by pixel (BIP) image encoding. The binary file is stored + such that Z-X "frames" are stored sequentially to form an image stack along the y-axis. When accessing the data sequentially on disk, + the dimensions read, from fastest to slowest, are Z, X, Y. + + This class is optimized for data streaming, and therefore supports extremely large (terabyte-scale) files. Data is loaded from disk + on request. Functions used to access data are written to support efficient reading. +*/ +template + +class bip: public hsi { + +protected: + + + //std::vector w; //band wavelength + unsigned long long offset; //header offset + + using hsi::w; //use the wavelength array in stim::hsi + using hsi::nnz; + using binary::progress; + using hsi::X; + using hsi::Y; + using hsi::Z; + +public: + + using binary::open; + using binary::file; + using binary::R; + using binary::read_line_0; + + bip(){ hsi::init_bip(); } + + /// Open a data file for reading using the class interface. + + /// @param filename is the name of the binary file on disk + /// @param X is the number of samples along dimension 1 + /// @param Y is the number of samples (lines) along dimension 2 + /// @param B is the number of samples (bands) along dimension 3 + /// @param header_offset is the number of bytes (if any) in the binary header + /// @param wavelengths is an optional STL vector of size B specifying a numerical label for each band + bool open(std::string filename, + unsigned long long X, + unsigned long long Y, + unsigned long long B, + unsigned long long header_offset, + std::vector wavelengths, + stim::iotype io = stim::io_in){ + + //copy the wavelengths to the BSQ file structure + w = wavelengths; + //copy the offset to the structure + offset = header_offset; + + return open(filename, vec(B, X, Y), header_offset, io); + + } + + /// Retrieve a single band (based on index) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param page <= B is the integer number of the band to be copied. + bool band_index( T * p, unsigned long long page, bool PROGRESS = false){ + return binary::read_plane_0(p, page, PROGRESS); + } + + /// Retrieve a single band (by numerical label) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param wavelength is a floating point value (usually a wavelength in spectral data) used as a label for the band to be copied. + bool band( T * p, double wavelength, bool PROGRESS = false){ + + //if there are no wavelengths in the BSQ file + if(w.size() == 0) + return band_index(p, (unsigned long long)wavelength, PROGRESS); + + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + + unsigned page=0; //bands around the wavelength + + + //get the bands numbers around the wavelength + + //if wavelength is smaller than the first one in header file + if ( w[page] > wavelength ){ + band_index(p, page, PROGRESS); + return true; + } + + while( w[page] < wavelength ) + { + page++; + //if wavelength is larger than the last wavelength in header file + if (page == Z()) { + band_index(p, Z()-1, PROGRESS); + return true; + } + } + if ( wavelength < w[page] ) + { + T * p1; + T * p2; + p1=(T*)malloc( XY * sizeof(T)); //memory allocation + p2=(T*)malloc( XY * sizeof(T)); + band_index(p1, page - 1); + band_index(p2, page, PROGRESS); + for(unsigned long long i=0; i < XY; i++){ + double r = (double) (wavelength - w[page-1]) / (double) (w[page] - w[page-1]); + p[i] = (T)(((double)p2[i] - (double)p1[i]) * r + (double)p1[i]); + } + free(p1); + free(p2); + } + else //if the wavelength is equal to a wavelength in header file + { + band_index(p, page, PROGRESS); + } + return true; + } + + /// Retrieve a single spectrum (Z-axis line) at a given (x, y) location and stores it in pre-allocated memory. + + /// @param p is a pointer to pre-allocated memory at least B * sizeof(T) in size. + /// @param x is the x-coordinate (dimension 1) of the spectrum. + /// @param y is the y-coordinate (dimension 2) of the spectrum. + bool spectrum(T * p, unsigned long long x, unsigned long long y, bool PROGRESS = false){ + return read_line_0(p, x, y, PROGRESS); //read a line in the binary YZ plane (dimension order for BIP is ZXY) + } + bool spectrum(T* p, size_t n, bool PROGRESS = false){ + size_t y = n / X(); + size_t x = n - y * X(); + return read_line_0(p, x, y, PROGRESS); //read a line in the binary YZ plane (dimension order for BIP is ZXY) + } + + /// Retrieves a band of x values from a given xz plane. + + /// @param p is a pointer to pre-allocated memory at least X * sizeof(T) in size + /// @param c is a pointer to an existing XZ plane (size X*Z*sizeof(T)) + /// @param wavelength is the wavelength of X values to retrieve + bool read_x_from_xz(T* p, T* c, double wavelength) + { + unsigned long long B = Z(); + + unsigned long long page=0; //samples around the wavelength + + + //get the bands numbers around the wavelength + + //if wavelength is smaller than the first one in header file + if ( w[page] > wavelength ){ + for(unsigned long long j = 0; j < X(); j++) + { + p[j] = c[j * B]; + } + return true; + } + + while( w[page] < wavelength ) + { + page++; + //if wavelength is larger than the last wavelength in header file + if (page == B) { + for(unsigned long long j = 0; j < X(); j++) + { + p[j] = c[(j + 1) * B - 1]; + } + return true; + } + } + if ( wavelength < w[page] ) + { + T * p1; + T * p2; + p1=(T*)malloc( X() * sizeof(T)); //memory allocation + p2=(T*)malloc( X() * sizeof(T)); + //band_index(p1, page - 1); + for(unsigned long long j = 0; j < X(); j++) + { + p1[j] = c[j * B + page - 1]; + } + //band_index(p2, page ); + for(unsigned long long j = 0; j < X(); j++) + { + p2[j] = c[j * B + page]; + } + + for(unsigned long long i=0; i < X(); i++){ + double r = (double) (wavelength - w[page-1]) / (double) (w[page] - w[page-1]); + p[i] = (p2[i] - p1[i]) * r + p1[i]; + } + free(p1); + free(p2); + } + else //if the wavelength is equal to a wavelength in header file + { + //band_index(p, page); + for(unsigned long long j = 0; j < X(); j++) + { + p[j] = c[j * B + page]; + } + } + + return true; + } + + /// Retrieve a single pixel and store it in a pre-allocated double array. + bool pixeld(double* p, unsigned long long n){ + unsigned long long bandnum = X() * Y(); //calculate numbers in one band + if ( n >= bandnum){ //make sure the pixel number is right + std::cout<<"ERROR: sample or line out of range"<= N){ //make sure the pixel number is right + std::cout<<"ERROR: sample or line out of range"<::read_plane_2(p, y); + } + + /// Perform baseline correction given a list of baseline points and stores the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param wls is the list of baseline points based on band labels. + bool baseline(std::string outname, std::vector base_pts, unsigned char* mask = NULL, bool PROGRESS = false){ + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + + unsigned long long N = X() * Y(); //calculate the total number of pixels to be processed + unsigned long long B = Z(); //get the number of bands + T* s = (T*)malloc(sizeof(T) * B); //allocate memory to store a pixel + T* sbc = (T*)malloc(sizeof(T) * B); //allocate memory to store the baseline corrected spectrum + + std::vector base_vals; //allocate space for the values at each baseline point + double aw, bw; //surrounding baseline point wavelengths + T av, bv; //surrounding baseline point values + unsigned long long ai, bi; //surrounding baseline point band indices + for(unsigned long long n = 0; n < N; n++){ //for each pixel in the image + if(mask != NULL && !mask[n]){ //if the pixel isn't masked + memset(sbc, 0, sizeof(T) * B); //set the baseline corrected spectrum to zero + } + else{ + + pixel(s, n); //retrieve the spectrum s + base_vals = hsi::interp_spectrum(s, base_pts); //get the values at each baseline point + + ai = bi = 0; + aw = w[0]; //initialize the current baseline points (assume the spectrum starts at 0) + av = s[0]; + bw = base_pts[0]; + for(unsigned long long b = 0; b < B; b++){ //for each band in the spectrum + while(bi < base_pts.size() && base_pts[bi] < w[b]) //while the current wavelength is higher than the second baseline point + bi++; //move to the next baseline point + if(bi < base_pts.size()){ + bw = base_pts[bi]; //set the wavelength for the upper bound baseline point + bv = base_vals[bi]; //set the value for the upper bound baseline point + } + if(bi == base_pts.size()){ //if we have passed the last baseline point + bw = w[B-1]; //set the outer bound to the last spectral band + bv = s[B-1]; + } + if(bi != 0){ + ai = bi - 1; //set the lower bound baseline point index + aw = base_pts[ai]; //set the wavelength for the lower bound baseline point + av = base_vals[ai]; //set the value for the lower bound baseline point + } + sbc[b] = s[b] - hsi::lerp(w[b], av, aw, bv, bw); //perform the baseline correction and save the new value + } + } + + if(PROGRESS) progress = (double)(n+1) / N * 100; //set the current progress + + target.write((char*)sbc, sizeof(T) * B); //write the corrected data into destination + } //end for each pixel + + free(s); + free(sbc); + target.close(); + + return true; + + } + + /// Normalize all spectra based on the value of a single band, storing the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param w is the label specifying the band that the hyperspectral image will be normalized to. + /// @param t is a threshold specified such that a spectrum with a value at w less than t is set to zero. Setting this threshold allows the user to limit division by extremely small numbers. + bool ratio(std::string outname, double w, unsigned char* mask = NULL, bool PROGRESS = false) + { + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + unsigned long long N = X() * Y(); //calculate the total number of pixels to be processed + unsigned long long B = Z(); //get the number of bands + T* s = (T*)malloc(sizeof(T) * B); //allocate memory to store a pixel + T nv; //stores the value of the normalized band + for(unsigned long long n = 0; n < N; n++){ //for each pixel in the image + if(mask != NULL && !mask[n]) //if the normalization band is below threshold + memset(s, 0, sizeof(T) * B); //set the output to zero + else{ + pixel(s, n); //retrieve the spectrum s + nv = hsi::interp_spectrum(s, w); //find the value of the normalization band + + for(unsigned long long b = 0; b < B; b++) //for each band in the spectrum + s[b] /= nv; //divide by the normalization value + } + + if(PROGRESS) progress = (double)(n+1) / N * 100; //set the current progress + + target.write((char*)s, sizeof(T) * B); //write the corrected data into destination + } //end for each pixel + + free(s); + target.close(); + return true; + } + + void normalize(std::string outfile, unsigned char* mask = NULL, bool PROGRESS = false){ + + std::ofstream target(outfile.c_str(), std::ios::binary); //open the target binary file + file.seekg(0, std::ios::beg); //move the pointer to the current file to the beginning + + size_t B = Z(); //number of spectral components + size_t XY = X() * Y(); //calculate the number of pixels + size_t Bb = B * sizeof(T); //number of bytes in a spectrum + + T* spec = (T*) malloc(Bb); //allocate space for the spectrum + T len; + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + memset(spec, 0, Bb); //set the spectrum to zero + if(mask == NULL || mask[xy]){ //if the pixel is masked + len = 0; //initialize the + file.read((char*)spec, Bb); //read a spectrum + for(size_t b = 0; b < B; b++) //for each band + len += spec[b]*spec[b]; //add the square of the spectral band + len = sqrt(len); //calculate the square of the sum of squared components + for(size_t b = 0; b < B; b++) //for each band + spec[b] /= len; //divide by the length + } + else + file.seekg(Bb, std::ios::cur); //otherwise skip a spectrum + target.write((char*)spec, Bb); //output the normalized spectrum + if(PROGRESS) progress = (double)(xy + 1) / (double)XY * 100; //update the progress + } + } + + /// This function loads a specified set of bands and saves them into a new output file + bool select(std::string outfile, std::vector bandlist, unsigned char* mask = NULL, bool PROGRESS = false) { + std::ofstream target(outfile.c_str(), std::ios::binary); //open the target binary file + if (!target) { + std::cout << "ERROR opening output file: " << outfile << std::endl; + return false; + } + file.seekg(0, std::ios::beg); //move the pointer to the current file to the beginning + + size_t B = Z(); //number of spectral components + size_t XY = X() * Y(); //calculate the number of pixels + size_t Bout = bandlist.size(); + size_t in_bytes = B * sizeof(T); //number of bytes in a spectrum + size_t out_bytes = Bout * sizeof(T); //number of bytes in an output spectrum + + T* in = (T*)malloc(in_bytes); //allocate space for the input spectrum + T* out = (T*)malloc(out_bytes); //allocate space for the output spectrum + + double wc; //register to store the desired wavelength + double w0, w1; //registers to store the wavelengths surrounding the given band + size_t b0, b1; //indices of the bands surrounding the specified wavelength + for (size_t xy = 0; xy < XY; xy++) { //for each pixel + //memset(out, 0, out_bytes); //set the spectrum to zero + if (mask == NULL || mask[xy]) { //if the pixel is masked + file.read((char*)in, in_bytes); //read an input spectrum + for (size_t b = 0; b < Bout; b++) { //for each band + wc = bandlist[b]; //set the desired wavelength + hsi::band_bounds(wc, b0, b1); //get the surrounding bands + w0 = w[b0]; //get the wavelength for the lower band + w1 = w[b1]; //get the wavelength for the higher band + out[b] = hsi::lerp(wc, in[b0], w0, in[b1], w1); //interpolate the spectral values to get the desired output value + } + } + else + file.seekg(Bout, std::ios::cur); //otherwise skip a spectrum + target.write((char*)out, out_bytes); //output the normalized spectrum + if (PROGRESS) progress = (double)(xy + 1) / (double)XY * 100; //update the progress + } + + free(in); + free(out); + return true; + } + + + /// Convert the current BIP file to a BIL file with the specified file name. + + /// @param outname is the name of the output BIL file to be saved to disk. + bool bil(std::string outname, bool PROGRESS = false) + { + unsigned long long S = X() * Z() * sizeof(T); //calculate the number of bytes in a ZX slice + + std::ofstream target(outname.c_str(), std::ios::binary); + //std::string headername = outname + ".hdr"; + + T * p; //pointer to the current ZX slice for bip file + p = (T*)malloc(S); + T * q; //pointer to the current XZ slice for bil file + q = (T*)malloc(S); + + for ( unsigned long long i = 0; i < Y(); i++) + { + read_plane_y(p, i); + for ( unsigned long long k = 0; k < Z(); k++) + { + unsigned long long ks = k * X(); + for ( unsigned long long j = 0; j < X(); j++) + q[ks + j] = p[k + j * Z()]; + + if(PROGRESS) progress = (double)(i * Z() + k+1) / (Y() * Z()) * 100; + } + target.write(reinterpret_cast(q), S); //write a band data into target file + } + + free(p); + free(q); + target.close(); + return true; + } + + /// Return a baseline corrected band given two adjacent baseline points and their bands. The result is stored in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lp is a pointer to an array holding the band image for the left baseline point + /// @param rp is a pointer to an array holding the band image for the right baseline point + /// @param wavelength is the label value for the requested baseline-corrected band + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size. + bool baseline_band(double lb, double rb, T* lp, T* rp, double wavelength, T* result){ + + unsigned long long XY = X() * Y(); + band(result, wavelength); //get band + + //perform the baseline correction + double r = (double) (wavelength - lb) / (double) (rb - lb); + for(unsigned long long i=0; i < XY; i++){ + result[i] =(T) (result[i] - (rp[i] - lp[i]) * r - lp[i] ); + } + return true; + } + + /// Return a baseline corrected band given two adjacent baseline points. The result is stored in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param bandwavelength is the label value for the desired baseline-corrected band + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size. + bool height(double lb, double rb, double bandwavelength, T* result){ + + T* lp; + T* rp; + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + + band(lp, lb); + band(rp, rb); + + baseline_band(lb, rb, lp, rp, bandwavelength, result); + + free(lp); + free(rp); + return true; + } + + + /// Calculate the area under the spectrum between two specified points and stores the result in a pre-allocated array. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label value for the left bound (start of the integration) + /// @param rab is the label value for the right bound (end of the integration) + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool area(double lb, double rb, double lab, double rab, T* result){ + + T* lp; //left band pointer + T* rp; //right band pointer + T* cur; //current band 1 + T* cur2; //current band 2 + + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + cur = (T*) malloc(S); + cur2 = (T*) malloc(S); + + memset(result, (char)0, S); + + //find the wavelenght position in the whole band + unsigned long long n = w.size(); + unsigned long long ai = 0; //left bound position + unsigned long long bi = n - 1; //right bound position + + + + //to make sure the left and the right bound are in the bandwidth + if (lb < w[0] || rb < w[0] || lb > w[n-1] || rb >w[n-1]){ + std::cout<<"ERROR: left bound or right bound out of bandwidth"< rb){ + std::cout<<"ERROR: right bound should be bigger than left bound"<= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); + band(rp, rb); + + //calculate the beginning and the ending part + baseline_band(lb, rb, lp, rp, rab, cur2); //ending part + baseline_band(lb, rb, lp, rp, w[bi], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + + //calculate the area + ai++; + for(unsigned long long i = ai; i <= bi ;i++) + { + baseline_band(lb, rb, lp, rp, w[ai], cur2); + for(unsigned long long j = 0; j < XY; j++) + { + result[j] += (T)((w[ai] - w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the ratio of two baseline-corrected peaks. The result is stored in a pre-allocated array. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool ph_to_ph(T* result, double lb1, double rb1, double pos1, double lb2, double rb2, double pos2, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the two peak band + height(lb1, rb1, pos1, p1); + height(lb2, rb2, pos2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between a peak area and peak height. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_ph(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double pos, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + height(lb2, rb2, pos, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between two peak areas. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param lab1 is the label value for the left bound (start of the integration) of the first peak (numerator) + /// @param rab1 is the label value for the right bound (end of the integration) of the first peak (numerator) + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param lab2 is the label value for the left bound (start of the integration) of the second peak (denominator) + /// @param rab2 is the label value for the right bound (end of the integration) of the second peak (denominator) + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_pa(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double lab2, double rab2, unsigned char* mask = NULL){ + + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + area(lb2, rb2, lab2, rab2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(p1[i] == 0 && p2[i] ==0) + result[i] = 1; + else + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the definite integral of a baseline corrected peak. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the definite integral + /// @param rab is the label for the end of the definite integral + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool x_area(double lb, double rb, double lab, double rab, T* result){ + T* lp; //left band pointer + T* rp; //right band pointer + T* cur; //current band 1 + T* cur2; //current band 2 + + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + cur = (T*) malloc(S); + cur2 = (T*) malloc(S); + + memset(result, (char)0, S); + + //find the wavelenght position in the whole band + unsigned long long n = w.size(); + unsigned long long ai = 0; //left bound position + unsigned long long bi = n - 1; //right bound position + + //to make sure the left and the right bound are in the bandwidth + if (lb < w[0] || rb < w[0] || lb > w[n-1] || rb >w[n-1]){ + std::cout<<"ERROR: left bound or right bound out of bandwidth"< rb){ + std::cout<<"ERROR: right bound should be bigger than left bound"<= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); + band(rp, rb); + + //calculate the beginning and the ending part + baseline_band(lb, rb, lp, rp, rab, cur2); //ending part + baseline_band(lb, rb, lp, rp, w[bi], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * (rab + w[bi]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * (w[ai] + lab) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + + //calculate f(x) times x + ai++; + for(unsigned long long i = ai; i <= bi ;i++) + { + baseline_band(lb, rb, lp, rp, w[ai], cur2); + for(unsigned long long j = 0; j < XY; j++) + { + result[j] += (T)((w[ai] - w[ai-1]) * (w[ai] + w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the centroid of a baseline corrected peak. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the peak + /// @param rab is the label for the end of the peak + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool centroid(T* result, double lb, double rb, double lab, double rab, unsigned char* mask = NULL){ + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + //get the area and the peak band + x_area(lb, rb, lab, rab, p1); + area(lb, rb, lab, rab, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]) + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Create a mask based on a given band and threshold value. + + /// All pixels in the + /// specified band greater than the threshold are true and all pixels less than the threshold are false. + /// @param mask_band is the band used to specify the mask + /// @param threshold is the threshold used to determine if the mask value is true or false + /// @param p is a pointer to a pre-allocated array at least X * Y in size + bool build_mask(unsigned char* out_mask, double mask_band, double lower, double upper, unsigned char* mask = NULL, bool PROGRESS = false){ + + T* temp = (T*)malloc(X() * Y() * sizeof(T)); //allocate memory for the certain band + band(temp, mask_band, PROGRESS); + + for (unsigned long long i = 0; i < X() * Y();i++) { + if(mask == NULL || mask[i] != 0){ + if(temp[i] > lower && temp[i] < upper){ + out_mask[i] = 255; + } + else + out_mask[i] = 0; + } + } + + free(temp); + return true; + + } + + /// Apply a mask file to the BSQ image, setting all values outside the mask to zero. + + /// @param outfile is the name of the masked output file + /// @param p is a pointer to memory of size X * Y, where p(i) = 0 for pixels that will be set to zero. + bool apply_mask(std::string outfile, unsigned char* p, bool PROGRESS = false){ + + std::ofstream target(outfile.c_str(), std::ios::binary); + + unsigned long long ZX = Z() * X(); //calculate the number of values in a page (XZ in BIP) + unsigned long long L = ZX * sizeof(T); //calculate the number of bytes in a page + + T * temp = (T*)malloc(L); //allocate space for that page + + for (unsigned long long i = 0; i < Y(); i++) //for each page (Y in BIP) + { + read_plane_y(temp, i); //load that page (it's pointed to by temp) + for ( unsigned long long j = 0; j < X(); j++) //for each X value + { + for (unsigned long long k = 0; k < Z(); k++) //for each B value (band) + { + if (p[i * X() + j] == 0) //if the mask value is zero + temp[j * Z() + k] = 0; //set the pixel value to zero + else //otherwise just continue + continue; + } + } + target.write(reinterpret_cast(temp), L); //write the edited band data into target file + if(PROGRESS) progress = (double)(i+1) / (double)Y() * 100; + } + target.close(); //close the target file + free(temp); //free allocated memory + return true; //return true + } + + /// Copies all spectra corresponding to nonzero values of a mask into a pre-allocated matrix of size (B x P) + /// where P is the number of masked pixels and B is the number of bands. The allocated memory can be accessed + /// using the following indexing: i = p*B + b + /// @param matrix is the destination for the pixel data + /// @param mask is the mask + bool sift(T* matrix, unsigned char* mask = NULL, bool PROGRESS = false){ + size_t Bbytes = sizeof(T) * Z(); + size_t XY = X() * Y(); + T* band = (T*) malloc( Bbytes ); //allocate space for a line + + file.seekg(0, std::ios::beg); //seek to the beginning of the file + + size_t p = 0; //create counter variables + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + if(mask == NULL || mask[xy]){ //if the current pixel is masked + file.read( (char*)band, Bbytes ); //read the current line + for(size_t b = 0; b < Z(); b++){ //copy each band value to the sifted matrix + size_t i = p * Z() + b; //calculate the index in the sifted matrix + matrix[i] = band[b]; //store the current value in the line at the correct matrix location + } + p++; //increment the pixel pointer + } + else + file.seekg(Bbytes, std::ios::cur); //otherwise skip this band + if(PROGRESS) progress = (double)(xy+1) / (double)XY * 100; + } + return true; + } + + /// Saves to disk only those spectra corresponding to mask values != 0 + bool sift(std::string outfile, unsigned char* mask, bool PROGRESS = false){ + + //reset the file pointer to the beginning of the file + file.seekg(0, std::ios::beg); + + // open an output stream + std::ofstream target(outfile.c_str(), std::ios::binary); + + //allocate space for a single spectrum + unsigned long long B = Z(); + T* spectrum = (T*) malloc(B * sizeof(T)); + + //calculate the number of pixels in a band + unsigned long long XY = X() * Y(); + + //for each pixel + unsigned long long skip = 0; //number of spectra to skip + for(unsigned long long x = 0; x < XY; x++){ + + //if the current pixel isn't masked + if( mask[x] == 0){ + //increment the number of skipped pixels + skip++; + } + //if the current pixel is masked + else{ + + //skip the intermediate pixels + file.seekg(skip * B * sizeof(T), std::ios::cur); + + //set the skip value to zero + skip = 0; + + //read this pixel into memory + file.read((char *)spectrum, B * sizeof(T)); + + //write this pixel out + target.write((char *)spectrum, B * sizeof(T)); + } + if(PROGRESS) progress = (double) (x+1) / XY * 100; + + } + + //close the output file + target.close(); + free(spectrum); + + return true; + } + + bool unsift(std::string outfile, unsigned char* mask, unsigned long long samples, unsigned long long lines, bool PROGRESS = false){ + + // open an output stream + std::ofstream target(outfile.c_str(), std::ios::binary); + + //reset the file pointer to the beginning of the file + file.seekg(0, std::ios::beg); + + //allocate space for a single spectrum + unsigned long long B = Z(); + T* spectrum = (T*) malloc(B * sizeof(T)); + + //allocate space for a spectrum of zeros + T* zeros = (T*) malloc(B * sizeof(T)); + memset(zeros, 0, B * sizeof(T)); + + //calculate the number of pixels in a band + unsigned long long XY = samples * lines; + + //for each pixel + unsigned long long skip = 0; //number of spectra to skip + for(unsigned long long x = 0; x < XY; x++){ + + //if the current pixel isn't masked + if( mask[x] == 0){ + + //write a bunch of zeros + target.write((char *)zeros, B * sizeof(T)); + } + //if the current pixel is masked + else{ + + //read a pixel into memory + file.read((char *)spectrum, B * sizeof(T)); + + //write this pixel out + target.write((char *)spectrum, B * sizeof(T)); + } + + if(PROGRESS) progress = (double)(x + 1) / XY * 100; + + } + + //close the output file + target.close(); + free(spectrum); + + //progress = 100; + + return true; + + + } + + /// Calculate the mean value for all masked (or valid) pixels in a band and returns the average spectrum + + /// @param p is a pointer to pre-allocated memory of size [B * sizeof(T)] that stores the mean spectrum + /// @param mask is a pointer to memory of size [X * Y] that stores the mask value at each pixel location + bool mean_spectrum(double* m, double* std = NULL, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long XY = X() * Y(); //calculate the total number of pixels in the HSI + T* temp = (T*)malloc(sizeof(T) * Z()); //allocate space for the current spectrum to be read + memset(m, 0, Z() * sizeof(double)); //set the mean spectrum to zero + double* e_x2 = (double*)malloc(Z() * sizeof(double)); //allocate space for E[x^2] + memset(e_x2, 0, Z() * sizeof(double)); //set all values for E[x^2] to zero + + unsigned long long count = nnz(mask); //calculate the number of masked pixels + double x; + for (unsigned long long i = 0; i < XY; i++){ //for each pixel in the HSI + if (mask == NULL || mask[i] != 0){ //if the pixel is masked + pixel(temp, i); //get the spectrum + for (unsigned long long j = 0; j < Z(); j++){ //for each spectral component + x = temp[j]; + m[j] += x / (double)count; //add the weighted value to the average + e_x2[j] += x*x / (double)count; + } + } + if(PROGRESS) progress = (double)(i+1) / XY * 100; //increment the progress + } + + //calculate the standard deviation + if (std != NULL) { + for (size_t i = 0; i < Z(); i++) + std[i] = sqrt(e_x2[i] - m[i] * m[i]); + } + + free(temp); + return true; + } +//#ifdef CUDA_FOUND + /// Calculate the covariance matrix for masked pixels using cuBLAS + /// Note that cuBLAS only supports integer-sized arrays, so there may be issues with large spectra + int co_matrix_cublas(double* co, double* avg, unsigned char *mask, bool PROGRESS = false){ + + cudaError_t cudaStat; + cublasStatus_t stat; + cublasHandle_t handle; + + unsigned long long XY = X() * Y(); //calculate the number of elements in a band image + unsigned long long B = Z(); //calculate the number of spectral elements + + double* s = (double*)malloc(sizeof(double) * B); //allocate space for the spectrum that will be pulled from the file + double* s_dev; //declare a device pointer that will store the spectrum on the GPU + double* A_dev; //declare a device pointer that will store the covariance matrix on the GPU + double* avg_dev; //declare a device pointer that will store the average spectrum + cudaStat = cudaMalloc(&s_dev, B * sizeof(double)); //allocate space on the CUDA device for the spectrum + cudaStat = cudaMalloc(&A_dev, B * B * sizeof(double)); //allocate space on the CUDA device for the covariance matrix + cudaStat = cudaMemset(A_dev, 0, B * B * sizeof(double)); //initialize the covariance matrix to zero (0) + cudaStat = cudaMalloc(&avg_dev, B * sizeof(double)); //allocate space on the CUDA device for the average spectrum + stat = cublasSetVector((int)B, sizeof(double), avg, 1, avg_dev, 1); //copy the average spectrum to the CUDA device + + double ger_alpha = 1.0/(double)XY; //scale the outer product by the inverse of the number of samples (mean outer product) + double axpy_alpha = -1; //multiplication factor for the average spectrum (in order to perform a subtraction) + + stat = cublasCreate(&handle); //create a cuBLAS instance + if (stat != CUBLAS_STATUS_SUCCESS) return 1; //test the cuBLAS instance to make sure it is valid + + //else std::cout<<"Using cuBLAS to calculate the mean covariance matrix..."<= 0){ //if a CUDA device is specified + int dev_count; + HANDLE_ERROR(cudaGetDeviceCount(&dev_count)); //get the number of CUDA devices + if(dev_count > 0 && dev_count > cuda_device){ //if the first device is not an emulator + cudaDeviceProp prop; + cudaGetDeviceProperties(&prop, cuda_device); //get the property of the requested CUDA device + if (prop.major != 9999) { + std::cout << "Using CUDA device [" << cuda_device << "] to calculate the mean covariance matrix..."<= 0) { //if a CUDA device is specified + int dev_count; + HANDLE_ERROR(cudaGetDeviceCount(&dev_count)); //get the number of CUDA devices + if (dev_count > 0 && dev_count > cuda_device) { //if the first device is not an emulator + cudaDeviceProp prop; + cudaGetDeviceProperties(&prop, cuda_device); //get the property of the requested CUDA device + if (prop.major != 9999) { + std::cout << "Using CUDA device [" << cuda_device << "] to calculate the noise covariance matrix..." << std::endl; + HANDLE_ERROR(cudaSetDevice(cuda_device)); + int status = coNoise_matrix_cublas(coN, avg, mask, PROGRESS); //use cuBLAS to calculate the covariance matrix + if (status == 0) return true; //if the cuBLAS function returned correctly, we're done + } + } //otherwise continue using the CPU + std::cout << "WARNING: cuBLAS failed, using CPU" << std::endl; + } + + progress = 0; + //memory allocation + unsigned long long XY = X() * Y(); + unsigned long long B = Z(); + T* temp = (T*)malloc(sizeof(T) * B); + T* temp2 = (T*)malloc(sizeof(T) * B); + + unsigned long long count = nnz(mask); //count the number of masked pixels + + //initialize covariance matrix of noise + memset(coN, 0, B * B * sizeof(double)); + + //calculate covariance matrix + double* coN_half = (double*)malloc(B * B * sizeof(double)); //allocate space for a higher-precision intermediate matrix + double* temp_precise = (double*)malloc(B * sizeof(double)); + double* temp_precise2 = (double*)malloc(B * sizeof(double)); + memset(coN_half, 0, B * B * sizeof(double)); //initialize the high-precision matrix with zeros + unsigned long long idx; //stores i*B to speed indexing + for (unsigned long long xy = 0; xy < XY; xy++) { + if (mask == NULL || mask[xy] != 0) { + pixel(temp, xy); //retreive the spectrum at the current xy pixel location + if (xy < XY - X()) { + pixel(temp2, xy + X()); //retreive the spectrum at the current xy+X pixel location, which is adjacent (bellow) to the pixel at xy location (in y direction) + } + else { + pixel(temp2, xy - X()); //for the last row we consider the the adjacent pixel which is located above pixel xy + } + for (unsigned long long b = 0; b < B; b++) { //subtract the mean from this spectrum and increase the precision + temp_precise[b] = (double)temp[b] - (double)avg[b]; + temp_precise2[b] = (double)temp2[b] - (double)avg[b]; + } + + for (unsigned long long b2 = 0; b2 < B; b2++) //Minimum/Maximum Autocorrelation Factors (MAF) method : subtranct each pixel from adjacent pixel (in y direction) + temp_precise[b2] -= temp_precise2[b2]; + + idx = 0; + for (unsigned long long b0 = 0; b0 < B; b0++) { //for each band + for (unsigned long long b1 = b0; b1 < B; b1++) + coN_half[idx++] += temp_precise[b0] * temp_precise[b1]; + } + } + if (PROGRESS) progress = (double)(xy + 1) / XY * 100; + } + idx = 0; + for (unsigned long long i = 0; i < B; i++) { //copy the precision matrix to both halves of the output matrix + for (unsigned long long j = i; j < B; j++) { + coN[j * B + i] = coN[i * B + j] = coN_half[idx++] / (double)count; + } + } + + free(temp); + free(temp2); + free(temp_precise); + free(temp_precise2); + return true; + } + + /// Project the spectra onto a set of basis functions + /// @param outfile is the name of the new binary output file that will be created + /// @param center is a spectrum about which the data set will be rotated (ex. when performing mean centering) + /// @param basis a set of basis vectors that the data set will be projected onto (after centering) + /// @param M is the number of basis vectors + /// @param mask is a character mask used to limit processing to valid pixels + bool project_cublas(std::string outfile, double* center, double* basis, unsigned long long M, unsigned char* mask = NULL, bool PROGRESS = false){ + + //cudaError_t cudaStat; + //cublasStatus_t stat; + cublasHandle_t handle; + + std::ofstream target(outfile.c_str(), std::ios::binary); //open the target binary file + + progress = 0; //initialize the progress to zero (0) + unsigned long long XY = X() * Y(); //calculate the number of elements in a band image + unsigned long long B = Z(); //calculate the number of spectral elements + + double* s = (double*)malloc(sizeof(double) * B); //allocate space for the spectrum that will be pulled from the file + double* s_dev; //declare a device pointer that will store the spectrum on the GPU + HANDLE_ERROR(cudaMalloc(&s_dev, B * sizeof(double))); //allocate space on the CUDA device for the spectrum + + + double* basis_dev; // device pointer on the GPU + HANDLE_ERROR(cudaMalloc(&basis_dev, M * B * sizeof(double))); // allocate space on the CUDA device + HANDLE_ERROR(cudaMemset(basis_dev, 0, M * B * sizeof(double))); // initialize basis_dev to zero (0) + + + /// transposing basis matrix (because cuBLAS is column-major) + double *basis_Transposed = (double*)malloc(M * B * sizeof(double)); + memset(basis_Transposed, 0, M * B * sizeof(double)); + for (int i = 0; i(temp), sizeof(T) * M); //write the projected vector + if(PROGRESS) progress = (double)(xy+1) / XY * 100; //record the current progress + + } + + //clean up allocated device memory + HANDLE_ERROR(cudaFree(A_dev)); + HANDLE_ERROR(cudaFree(s_dev)); + HANDLE_ERROR(cudaFree(basis_dev)); + HANDLE_ERROR(cudaFree(center_dev)); + CUBLAS_HANDLE_ERROR(cublasDestroy(handle)); + free(A); + free(s); + free(temp); + target.close(); //close the output file + + return true; + } + + /// Project the spectra onto a set of basis functions + /// @param outfile is the name of the new binary output file that will be created + /// @param center is a spectrum about which the data set will be rotated (ex. when performing mean centering) + /// @param basis a set of basis vectors that the data set will be projected onto (after centering) + /// @param M is the number of basis vectors + /// @param mask is a character mask used to limit processing to valid pixels + bool project(std::string outfile, double* center, double* basis, unsigned long long M, unsigned char* mask = NULL, int cuda_device = 0, bool PROGRESS = false){ + if (cuda_device >= 0) { //if a CUDA device is specified + int dev_count; + HANDLE_ERROR(cudaGetDeviceCount(&dev_count)); //get the number of CUDA devices + if (dev_count > 0 && dev_count > cuda_device) { //if the first device is not an emulator + cudaDeviceProp prop; + cudaGetDeviceProperties(&prop, cuda_device); //get the property of the requested CUDA device + if (prop.major != 9999) { + std::cout << "Using CUDA device [" << cuda_device << "] to perform a basis projection..." << std::endl; + HANDLE_ERROR(cudaSetDevice(cuda_device)); + return project_cublas(outfile, center, basis, M, mask, PROGRESS); + } + } //otherwise continue using the CPU + std::cout << "WARNING: cuBLAS failed, using CPU" << std::endl; + } + + std::ofstream target(outfile.c_str(), std::ios::binary); //open the target binary file + //std::string headername = outfile + ".hdr"; //the header file name + + //memory allocation + unsigned long long XY = X() * Y(); + unsigned long long B = Z(); + + T* s = (T*)malloc(sizeof(T) * B); //allocate space for the spectrum + T* rs = (T*)malloc(sizeof(T) * M); //allocate space for the projected spectrum + double* bv; //pointer to the current projection vector + for(unsigned long long xy = 0; xy < XY; xy++){ //for each spectrum in the image + memset(rs, 0, sizeof(T) * M); + if(mask == NULL || mask[xy]){ + pixel(s, xy); //load the spectrum + for(unsigned long long m = 0; m < M; m++){ //for each basis vector + bv = &basis[m * B]; //assign 'bv' to the beginning of the basis vector + for(unsigned long long b = 0; b < B; b++){ //for each band + rs[m] += (T)(((double)s[b] - center[b]) * bv[b]); //center the spectrum and perform the projection + } + } + } + + target.write(reinterpret_cast(rs), sizeof(T) * M); //write the projected vector + if(PROGRESS) progress = (double)(xy+1) / XY * 100; + } + + free(s); //free temporary storage arrays + free(rs); + target.close(); //close the output file + + return true; + } + + bool inverse(std::string outfile, double* center, double* basis, unsigned long long B, unsigned long long C = 0, bool PROGRESS = false){ + + std::ofstream target(outfile.c_str(), std::ios::binary); //open the target binary file + std::string headername = outfile + ".hdr"; //the header file name + + //memory allocation + unsigned long long XY = X() * Y(); + if(C == 0) C = Z(); //if no coefficient number is given, assume all are used + C = std::min(C, Z()); //set the number of coefficients (the user can specify fewer) + + T* coeff = (T*)malloc(sizeof(T) * Z()); //allocate space for the coefficients + T* s = (T*)malloc(sizeof(T) * B); //allocate space for the spectrum + double* bv; //pointer to the current projection vector + for(unsigned long long xy = 0; xy < XY; xy++){ //for each pixel in the image (expressed as a set of coefficients) + pixel(coeff, xy); //load the coefficients + memset(s, 0, sizeof(T) * B); //initialize the spectrum to zero (0) + for(unsigned long long c = 0; c < C; c++){ //for each basis vector coefficient + bv = &basis[c * B]; //assign 'bv' to the beginning of the basis vector + for(unsigned long long b = 0; b < B; b++){ //for each component of the basis vector + s[b] += (T)((double)coeff[c] * bv[b] + center[b]); //calculate the contribution of each element of the basis vector in the final spectrum + } + } + + target.write(reinterpret_cast(s), sizeof(T) * B); //write the projected vector + if(PROGRESS) progress = (double)(xy+1) / XY * 100; + } + + free(coeff); //free temporary storage arrays + free(s); + target.close(); //close the output file + + return true; + } + + + /// Crop a region of the image and save it to a new file. + + /// @param outfile is the file name for the new cropped image + /// @param x0 is the lower-left x pixel coordinate to be included in the cropped image + /// @param y0 is the lower-left y pixel coordinate to be included in the cropped image + /// @param x1 is the upper-right x pixel coordinate to be included in the cropped image + /// @param y1 is the upper-right y pixel coordinate to be included in the cropped image + bool crop(std::string outfile, unsigned long long x0, + unsigned long long y0, + unsigned long long x1, + unsigned long long y1, + unsigned long long b0, + unsigned long long b1, + bool PROGRESS = false){ + + //calculate the new number of samples, lines, and bands + unsigned long long samples = x1 - x0 + 1; + unsigned long long lines = y1 - y0 + 1; + unsigned long long bands = b1 - b0 + 1; + + //calculate the length of one cropped spectrum + unsigned long long L = bands * sizeof(T); + + //unsigned long long L = Z() * sizeof(T); + + //allocate space for the spectrum + char* temp = (char*)malloc(L); + + //open an output file for binary writing + std::ofstream out(outfile.c_str(), std::ios::binary); + + //seek to the first pixel in the cropped image + size_t startx = x0 * Z(); + size_t starty = y0 * X() * Z(); + size_t startb = b0; + file.seekg( (starty + startx + startb) * sizeof(T), std::ios::beg); + + //distance between sample spectra in the same line + size_t dist_between_samples = Z() - bands; + size_t jump_sample = dist_between_samples * sizeof(T); + + //distance between sample spectra in adjacent lines + //unsigned long long jump_line = ( X() - x1 + x0 ) * Z() * sizeof(T); + size_t dist_between_lines = X() - samples; + size_t jump_line = dist_between_lines * Z() * sizeof(T); + + + //unsigned long long sp = y0 * X() + x0; //start pixel + + //for each pixel in the image + for (unsigned y = 0; y < lines; y++) { + for (unsigned x = 0; x < samples; x++) { + //read the cropped spectral region + file.read(temp, L ); + //pixel(temp, sp + x + y * X()); + out.write(temp, L); //write slice data into target file + + file.seekg(jump_sample, std::ios::cur); + + if(PROGRESS) progress = (double)(y * samples + x + 1) / (lines * samples) * 100; + } + + file.seekg(jump_line, std::ios::cur); + } + free(temp); + out.close(); + + return true; + } + + /// Remove a list of bands from the ENVI file + + /// @param outfile is the file name for the output hyperspectral image (with trimmed bands) + /// @param b is an array of bands to be eliminated + void trim(std::string outfile, std::vector band_array, bool PROGRESS = false){ + + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of the input file + + size_t B = Z(); //calculate the number of elements in a spectrum + size_t Bdst = Z() - band_array.size(); //calculate the number of elements in an output spectrum + size_t Bb = B * sizeof(T); //calculate the number of bytes in a spectrum + size_t XY = X() * Y(); //calculate the number of pixels in the image + T* src = (T*)malloc(Bb); //allocate space to store an input spectrum + T* dst = (T*)malloc(Bdst * sizeof(T)); //allocate space to store an output spectrum + + size_t i; //index into the band array + size_t bdst; //index into the output array + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + i = 0; + bdst = 0; + file.read((char*)src, Bb); //read a spectrum + for(size_t b = 0; b < B; b++){ //for each band + if(b != band_array[i]){ //if the band isn't trimmed + dst[bdst] = src[b]; //copy the band value to the output spectrum + bdst++; + } + else i++; //otherwise increment i + } + out.write((char*)dst, Bdst * sizeof(T)); //write the output spectrum + if(PROGRESS) progress = (double)(xy + 1) / (double) XY * 100; + } + free(src); + free(dst); + } + + /// Combine two BIP images along the Y axis + + /// @param outfile is the combined file to be output + /// @param infile is the input file stream for the image to combine with this one + /// @param Sx is the size of the second image along X + /// @param Sy is the size of the second image along Y + /// @param offset is a shift (negative or positive) in the combined image to the left or right + void combine(std::string outfile, bip* C, long long xp, long long yp, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + + size_t S[2]; //size of the output band image + size_t p0[2]; //position of the current image in the output + size_t p1[2]; //position of the source image in the output + + hsi::calc_combined_size(xp, yp, C->X(), C->Y(), S[0], S[1], p0[0], p0[1], p1[0], p1[1]); //calculate the image placement parameters + + size_t spec_bytes = Z() * sizeof(T); //calculate the number of bytes in a spectrum + T* spec = (T*)malloc(spec_bytes); //allocate space for a spectrum + + for(size_t y = 0; y < S[1]; y++){ //for each pixel in the destination image + for(size_t x = 0; x < S[0]; x++){ + if(x >= p0[0] && x < p0[0] + X() && y >= p0[1] && y < p0[1] + Y()) //if this pixel is in the current image + file.read((char*)spec, spec_bytes); + else if(x >= p1[0] && x < p1[0] + C->X() && y >= p1[1] && y < p1[1] + C->Y()) //if this pixel is in the source image + C->file.read((char*)spec, spec_bytes); + else + memset(spec, 0, spec_bytes); + out.write((char*)spec, spec_bytes); //write to the output file + } + if(PROGRESS) progress = (double)( (y+1) * S[0] + 1) / (double) (S[0] * S[1]) * 100; + } + } + + ///Append two files together along the band dimension + void append(std::string outfile, bip* C, bool PROGRESS = false) { + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + size_t a_bytes = Z() * sizeof(T); //calculate the number of bytes in a single plane of this file + size_t b_bytes = C->Z() * sizeof(T); //calculate the number of bytes in a single plane of the appending file + T* a = (T*)malloc(a_bytes); //allocate space for a plane of the current file + T* b = (T*)malloc(b_bytes); //allocate space for a plane of the appended file + if (PROGRESS) progress = 0; + for (size_t xy = 0; xy < X()*Y(); xy++) { + spectrum(a, xy); //read a plane from the current file + out.write((char*)a, a_bytes); //write the plane to disk + C->spectrum(b, xy); //read a plane from the appending file + out.write((char*)b, b_bytes); + if (PROGRESS) progress = (double)(xy + 1) / (double)(X() * Y()) * 100; + } + + out.close(); + } + /// Convolve the given band range with a kernel specified by a vector of coefficients. + + /// @param outfile is an already open stream to the output file + /// @param C is an array of coefficients + /// @param start is the band to start processing (the first coefficient starts here) + /// @param nbands is the number of bands to process + /// @param center is the index for the center coefficient for the kernel (used to set the wavelengths in the output file) + + void convolve(std::string outfile, std::vector C, size_t start, size_t end, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + + size_t N = end - start + 1; //number of bands in the output spectrum + size_t Nb = N * sizeof(T); //size of the output spectrum in bytes + size_t B = Z(); //calculate the number of values in a spectrum + size_t Bb = B * sizeof(T); //calculate the size of a spectrum in bytes + + file.seekg(0, std::ios::beg); //move to the beginning of the input file + + size_t nC = C.size(); //get the number of bands that the kernel spans + T* inspec = (T*)malloc(Bb); //allocate space for the input spectrum + T* outspec = (T*)malloc(Nb); //allocate space for the output spectrum + + size_t XY = X() * Y(); //number of pixels in the image + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + file.read((char*)inspec, Bb); //read an input spectrum + memset(outspec, 0, Nb); //set the output spectrum to zero (0) + if(mask == NULL || mask[xy]){ + for(size_t b = 0; b < N; b++){ //for each component of the spectrum + for(size_t c = 0; c < nC; c++){ //for each coefficient in the kernel + outspec[b] += (T)(inspec[b + start + c] * C[c]); //perform the sum/multiply part of the convolution + } + } + } + out.write((char*)outspec, Nb); //output the band + if(PROGRESS) progress = (double)(xy+1) / (double)XY * 100; + } + } + + void deriv(std::string outfile, size_t d, size_t order, const std::vector w, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + + + size_t B = Z(); //calculate the number of values in a spectrum + size_t Bb = B * sizeof(T); //calculate the size of a spectrum in bytes + + bool UNIFORM = true; + double ds = w[1] - w[0]; //initialize ds + for(size_t b = 1; b < B; b++) //test to see if the spectral spacing is uniform (if it is, convolution is much faster) + if(w[b] - w[b-1] != ds) UNIFORM = false; + + size_t nC = order + d; //approximating a derivative requires order + d samples + + file.seekg(0, std::ios::beg); //move to the beginning of the input file + + T* inspec = (T*)malloc(Bb); //allocate space for the input spectrum + T* outspec = (T*)malloc(Bb); //allocate space for the output spectrum + + size_t XY = X() * Y(); //number of pixels in the image + size_t mid = (size_t)(nC / 2); //calculate the mid point of the kernel + size_t iw; //index to the first wavelength used to evaluate the derivative at this band + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + file.read((char*)inspec, Bb); //read an input spectrum + memset(outspec, 0, Bb); //set the output spectrum to zero (0) + if(mask == NULL || mask[xy]){ + iw = 0; + for(size_t b = 0; b < mid; b++){ //for each component of the spectrum + std::vector w_pts(w.begin() + iw, w.begin() + iw + nC); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[b], w_pts, d); //get the optimal sample weights + for(size_t c = 0; c < nC; c++) //for each coefficient in the kernel + outspec[b] += (T)(inspec[iw + c] * C[c]); //perform the sum/multiply part of the convolution + } + std::vector w_pts(w.begin(), w.begin() + nC); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[0], w_pts, d); //get the optimal sample weights + for(size_t b = mid; b <= B - (nC - mid); b++){ + iw = b - mid; + if(!UNIFORM){ //if the spacing is non-uniform, we have to re-calculate these points every iteration + std::vector w_pts(w.begin() + iw, w.begin() + iw + nC); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[b], w_pts, d); //get the optimal sample weights + } + for(size_t c = 0; c < nC; c++) //for each coefficient in the kernel + outspec[b] += (T)(inspec[iw + c] * C[c]); //perform the sum/multiply part of the convolution + } + iw = B - nC; + for(size_t b = B - (nC - mid) + 1; b < B; b++){ + std::vector w_pts(w.begin() + iw, w.begin() + iw + nC); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[b], w_pts, d); //get the optimal sample weights + for(size_t c = 0; c < nC; c++) //for each coefficient in the kernel + outspec[b] += (T)(inspec[iw + c] * C[c]); //perform the sum/multiply part of the convolution + } + } + out.write((char*)outspec, Bb); //output the band + if(PROGRESS) progress = (double)(xy+1) / (double)XY * 100; + } + } + + bool multiply(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + unsigned long long N = X() * Y(); //calculate the total number of pixels to be processed + unsigned long long B = Z(); //get the number of bands + T* s = (T*)malloc(sizeof(T) * B); //allocate memory to store a pixel + for(unsigned long long n = 0; n < N; n++){ //for each pixel in the image + if(mask == NULL || mask[n]){ //if the pixel is masked + for(size_t b = 0; b < B; b++) //for each band in the spectrum + s[b] *= (T)v; //multiply + } + + if(PROGRESS) progress = (double)(n+1) / N * 100; //set the current progress + + target.write((char*)s, sizeof(T) * B); //write the corrected data into destination + } //end for each pixel + + free(s); //free the spectrum + target.close(); //close the output file + return true; + } + + bool add(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + unsigned long long N = X() * Y(); //calculate the total number of pixels to be processed + unsigned long long B = Z(); //get the number of bands + T* s = (T*)malloc(sizeof(T) * B); //allocate memory to store a pixel + for(unsigned long long n = 0; n < N; n++){ //for each pixel in the image + if(mask == NULL || mask[n]){ //if the pixel is masked + for(size_t b = 0; b < B; b++) //for each band in the spectrum + s[b] += (T)v; //multiply + } + + if(PROGRESS) progress = (double)(n+1) / N * 100; //set the current progress + + target.write((char*)s, sizeof(T) * B); //write the corrected data into destination + } //end for each pixel + + free(s); //free the spectrum + target.close(); //close the output file + return true; + } + + int fft(std::string outname, size_t bandmin, size_t bandmax, size_t samples = 0, T* ratio = NULL, size_t rx = 0, size_t ry = 0, bool PROGRESS = false, int device = 0){ + if(device == -1){ + std::cout<<"ERROR: GPU required for FFT (uses cuFFT)."< Z()){ + std::cout<<"ERROR: stim::envi doesn't support FFT padding just yet."<= nd){ //test for the existence of the requested device + std::cout<<"ERROR: requested CUDA device for stim::envi::fft() doesn't exist"<* batch_fft = (std::complex*) malloc(fft_bytes); + T* gpu_batch; //device pointer to the batch + HANDLE_ERROR(cudaMalloc(&gpu_batch, batch_bytes)); //allocate space on the device for the FFT batch + cufftComplex* gpu_batch_fft; //allocate space for the FFT result + HANDLE_ERROR(cudaMalloc(&gpu_batch_fft, fft_bytes)); + int N[1]; //create an array with the interferogram size (required for cuFFT input) + N[0] = (int)S; //set the only array value to the interferogram size + + //if a background is provided for a ratio + std::complex* ratio_fft = NULL; //create a pointer for the FFT of the ratio image (if it exists) + if(ratio){ + size_t bkg_bytes = rx * ry * S * sizeof(T); //calculate the total number of bytes in the background image + T* bkg_copy = (T*) malloc(bkg_bytes); //allocate space to copy the background + if(S == Z()) memcpy(bkg_copy, ratio, bkg_bytes); //if the number of samples used in processing equals the number of available samples + else{ + for(size_t xyi = 0; xyi < rx*ry; xyi++) + memcpy(&bkg_copy[xyi * S], &ratio[xyi * B], S * sizeof(T)); + } + T* gpu_ratio; + HANDLE_ERROR(cudaMalloc(&gpu_ratio, bkg_bytes)); + HANDLE_ERROR(cudaMemcpy(gpu_ratio, bkg_copy, bkg_bytes, cudaMemcpyHostToDevice)); + cufftHandle bkg_plan; + CUFFT_HANDLE_ERROR(cufftPlanMany(&bkg_plan, 1, N, NULL, 1, N[0], NULL, 1, N[0], CUFFT_R2C, (int)(rx * ry))); + size_t bkg_fft_bytes = rx * ry * (S / 2 + 1) * sizeof(cufftComplex); + T* gpu_ratio_fft; + HANDLE_ERROR(cudaMalloc(&gpu_ratio_fft, bkg_fft_bytes)); + CUFFT_HANDLE_ERROR(cufftExecR2C(bkg_plan, (cufftReal*)gpu_ratio, (cufftComplex*)gpu_ratio_fft)); + ratio_fft = (std::complex*) malloc(bkg_fft_bytes); + HANDLE_ERROR(cudaMemcpy(ratio_fft, gpu_ratio_fft, bkg_fft_bytes, cudaMemcpyDeviceToHost)); + HANDLE_ERROR(cudaFree(gpu_ratio)); + HANDLE_ERROR(cudaFree(gpu_ratio_fft)); + CUFFT_HANDLE_ERROR(cufftDestroy(bkg_plan)); + } + + cufftHandle plan; //create a CUFFT plan + CUFFT_HANDLE_ERROR(cufftPlanMany(&plan, 1, N, NULL, 1, N[0], NULL, 1, N[0], CUFFT_R2C, (int)nS)); + + std::ofstream outfile(outname, std::ios::binary); //open a file for writing + + size_t XY = X() * Y(); //calculate the number of spectra + size_t xy = 0; + size_t bs; //stores the number of spectra in the current batch + size_t s, b; + size_t S_fft = S/2 + 1; + size_t bandkeep = bandmax - bandmin + 1; + size_t x, y; + size_t ratio_i; + T* temp_spec = (T*) malloc(Z() * sizeof(T)); //allocate space to hold a single pixel + while(xy < XY){ //while there are unprocessed spectra + bs = (std::min)(XY - xy, nS); //calculate the number of spectra to include in the batch + for(s = 0; s < bs; s++){ //for each spectrum in the batch + pixel(temp_spec, xy + s); //read a pixel from disk + memcpy(&batch[s * S], temp_spec, S * sizeof(T)); + //pixel(&batch[s * S], xy + s); //read the next spectrum + } + HANDLE_ERROR(cudaMemcpy(gpu_batch, batch, batch_bytes, cudaMemcpyHostToDevice)); + CUFFT_HANDLE_ERROR(cufftExecR2C(plan, (cufftReal*)gpu_batch, gpu_batch_fft)); //execute the (implicitly forward) transform + HANDLE_ERROR(cudaMemcpy(batch_fft, gpu_batch_fft, fft_bytes, cudaMemcpyDeviceToHost)); //copy the data back to the GPU + for(s = 0; s < bs; s++){ //for each spectrum in the batch + y = (xy + s)/X(); + x = xy + s - y * X(); + if(ratio_fft) ratio_i = (y % ry) * rx + (x % rx); //if a background is used, calculate the coordinates into it + for(b = 0; b < S/2 + 1; b++){ //for each sample + if(ratio_fft) + batch[s * S + b] = -log(abs(batch_fft[s * S_fft + b]) / abs(ratio_fft[ratio_i * S_fft + b])); + else + batch[s * S + b] = abs(batch_fft[s * S_fft + b]); //calculate the magnitude of the spectrum + } + outfile.write((char*)&batch[s * S + bandmin], bandkeep * sizeof(T)); //save the resulting spectrum + } + xy += bs; //increment xy by the number of spectra processed + if(PROGRESS) progress = (double)xy / (double)XY * 100; + } + outfile.close(); + free(ratio_fft); + free(batch_fft); + free(batch); + HANDLE_ERROR(cudaFree(gpu_batch)); + HANDLE_ERROR(cudaFree(gpu_batch_fft)); + return 0; + } + + /// Close the file. + bool close(){ + file.close(); + return true; + } + + }; +} + +#endif diff --git a/tira/envi/bsq.h b/tira/envi/bsq.h new file mode 100644 index 0000000..852474c --- /dev/null +++ b/tira/envi/bsq.h @@ -0,0 +1,1607 @@ +#ifndef STIM_BSQ_H +#define STIM_BSQ_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace stim{ + +/** + The BIP class represents a 3-dimensional binary file stored using band sequential (BSQ) image encoding. The binary file is stored + such that X-Y "frames" are stored sequentially to form an image stack along the z-axis. When accessing the data sequentially on disk, + the dimensions read, from fastest to slowest, are X, Y, Z. + + This class is optimized for data streaming, and therefore supports extremely large (terabyte-scale) files. Data is loaded from disk + on request. Functions used to access data are written to support efficient reading. +*/ +template + +class bsq: public hsi { + + +protected: + + //std::vector w; //band wavelengths + unsigned long long offset; + + using binary::R; + + using hsi::w; //use the wavelength array in stim::hsi + using hsi::nnz; + using binary::progress; + using hsi::X; + using hsi::Y; + using hsi::Z; + +public: + + using binary::open; + using binary::file; + using binary::read_line_2; + using binary::read_plane_2; + + bsq(){ hsi::init_bsq(); } + + /// Open a data file for reading using the class interface. + + /// @param filename is the name of the binary file on disk + /// @param X is the number of samples along dimension 1 + /// @param Y is the number of samples (lines) along dimension 2 + /// @param B is the number of samples (bands) along dimension 3 + /// @param header_offset is the number of bytes (if any) in the binary header + /// @param wavelengths is an STL vector of size B specifying a numerical label for each band + bool open(std::string filename, + unsigned long long X, + unsigned long long Y, + unsigned long long B, + unsigned long long header_offset, + std::vector wavelengths, + stim::iotype io = stim::io_in){ + + //copy the wavelengths to the BSQ file structure + w = wavelengths; + //copy the wavelengths to the structure + offset = header_offset; + + return open(filename, vec(X, Y, B), header_offset, io); + } + + /// Retrieve a single band (based on index) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param page <= B is the integer number of the band to be copied. + bool band_index( T * p, unsigned long long page){ + return read_plane_2(p, page); //call the binary read_plane function (don't let it update the progress) + } + + /// Retrieve a single band (by numerical label) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param wavelength is a floating point value (usually a wavelength in spectral data) used as a label for the band to be copied. + bool band( T * p, double wavelength, bool PROGRESS = false){ + if(PROGRESS) progress = 0; + //if there are no wavelengths in the BSQ file + if(w.size() == 0){ + band_index(p, (unsigned long long)wavelength); + if(PROGRESS) progress = 100; + return true; + } + + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long page = 0; + + + //get the two neighboring bands (above and below 'wavelength') + + //if wavelength is smaller than the first one in header file + if ( w[page] > wavelength ){ + band_index(p, page); + if(PROGRESS) progress = 100; + return true; + } + + while( w[page] < wavelength ) + { + page++; + //if wavelength is larger than the last wavelength in the header file + // (the wavelength is out of bounds) + if (page == Z()) { + band_index(p, Z()-1); //return the last band + if(PROGRESS) progress = 100; + return true; + } + } + //when the page counter points to the first band above 'wavelength' + if ( wavelength < w[page] ){ + + //do the interpolation + T * p1; + T * p2; + p1=(T*)malloc( XY * sizeof(T)); //memory allocation + p2=(T*)malloc( XY * sizeof(T)); + band_index(p1, page - 1); + band_index(p2, page ); + for(unsigned long long i=0; i < XY; i++){ + double r = (wavelength - w[page-1]) / (w[page] - w[page-1]); + p[i] = (T)(((double)p2[i] - (double)p1[i]) * r + (double)p1[i]); + } + free(p1); + free(p2); + } + //if the wavelength is equal to a wavelength in header file + else{ + band_index(p, page); //return the band + } + if(PROGRESS) progress = 100; + return true; + } + + /// Retrieve a single spectrum (Z-axis line) at a given (x, y) location and stores it in pre-allocated memory. + + /// @param p is a pointer to pre-allocated memory at least B * sizeof(T) in size. + /// @param x is the x-coordinate (dimension 1) of the spectrum. + /// @param y is the y-coordinate (dimension 2) of the spectrum. + void spectrum(T* p, size_t n, bool PROGRESS){ + read_line_2(p, n, PROGRESS); + } + void spectrum(T * p, unsigned long long x, unsigned long long y, bool PROGRESS = false){ + read_line_2(p, x, y, PROGRESS); + } + + + /// Retrieve a single pixel and stores it in pre-allocated memory. + + /// @param p is a pointer to pre-allocated memory at least sizeof(T) in size. + /// @param n is an integer index to the pixel using linear array indexing. + bool pixel(T * p, unsigned long long n){ + + unsigned long long bandnum = X() * Y(); //calculate numbers in one band + if ( n >= bandnum){ //make sure the pixel number is right + std::cout<<"ERROR: sample or line out of range"<::header, std::ios::beg); //point to the certain pixel + for (unsigned long long i = 0; i < Z(); i++) + { + file.read((char *)(p + i), sizeof(T)); + file.seekg((bandnum - 1) * sizeof(T), std::ios::cur); //go to the next band + } + + return true; + } + + /// Perform baseline correction given a list of baseline points and stores the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param wls is the list of baseline points based on band labels. + bool baseline(std::string outname, std::vector wls, unsigned char* mask = NULL, bool PROGRESS = false ) + { + size_t N = wls.size(); //get the number of baseline points + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + //simplify image resolution + unsigned long long B = Z(); //calculate the number of bands + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + + double ai, bi; //stores the two baseline points wavelength surrounding the current band + double ci; //stores the current band's wavelength + + unsigned long long control=0; + + T * a; //pointers to the high and low band images + T * b; + T * c; //pointer to the current image + + a = (T*)malloc( S ); //memory allocation + b = (T*)malloc( S ); + c = (T*)malloc( S ); + + if (a == NULL || b == NULL || c == NULL){ + std::cout<<"ERROR: error allocating memory"; + exit(1); + } + + + //initialize lownum, highnum, low, high + ai=w[0]; + + //if no baseline point is specified at band 0, + //set the baseline point at band 0 to 0 + if(wls[0] != w[0]){ + bi = wls[control]; + memset(a, (char)0, S); + } + //else get the low band + else{ + control += 1; + band(a, ai); + bi = wls[control]; + } + //get the high band + band(b, bi); + + //correct every band + for(unsigned long long cii = 0; cii < B; cii++){ + + //update baseline points, if necessary + if( w[cii] >= bi && cii != B - 1) { + //if the high band is now on the last BL point? + if (control != N-1) { + + control++; //increment the index + + std::swap(a, b); //swap the baseline band pointers + + ai = bi; + bi = wls[control]; + band(b, bi); + + } + //if the last BL point on the last band of the file? + else if ( wls[control] < w[B - 1]) { + + std::swap(a, b); //swap the baseline band pointers + + memset(b, (char)0, S); //clear the high band + + ai = bi; + bi = w[B - 1]; + } + } + + //get the current band + band_index(c, cii); + ci = w[cii]; + + //perform the baseline correction + for(unsigned long long i=0; i < XY; i++){ + if(mask != NULL && !mask[i]) //if the pixel is excluded by a mask + c[i] = 0; //set the value to zero + else{ + double r = (double) (ci - ai) / (double) (bi - ai); + c[i] =(T) ( c[i] - (b[i] - a[i]) * r - a[i] ); + } + } + + target.write(reinterpret_cast(c), S); //write the corrected data into destination + + if(PROGRESS)progress = (double)(cii+1) / B * 100; + + } + + //header.save(headername); //save the new header file + + free(a); + free(b); + free(c); + target.close(); + + return true; + } + + /// Normalize all spectra based on the value of a single band, storing the result in a new BSQ file. + + /// @param outname is the name of the output file used to store the resulting baseline-corrected data. + /// @param w is the label specifying the band that the hyperspectral image will be normalized to. + /// @param t is a threshold specified such that a spectrum with a value at w less than t is set to zero. Setting this threshold allows the user to limit division by extremely small numbers. + bool ratio(std::string outname, double w, unsigned char* mask = NULL, bool PROGRESS = false) + { + unsigned long long B = Z(); //calculate the number of bands + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + T * b; //pointers to the certain wavelength band + T * c; //pointer to the current image + + b = (T*)malloc( S ); //memory allocation + c = (T*)malloc( S ); + + band(b, w); //get the certain band into memory + + for(unsigned long long j = 0; j < B; j++) + { + band_index(c, j); //get the current band into memory + for(unsigned long long i = 0; i < XY; i++) + { + if(mask != NULL && !mask[i]) + c[i] = (T)0.0; + else + c[i] = c[i] / b[i]; + } + target.write(reinterpret_cast(c), S); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / B * 100; + } + + + + //header.save(headername); //save the new header file + + free(b); + free(c); + target.close(); + return true; + } + + void normalize(std::string outfile, unsigned char* mask = NULL, bool PROGRESS = false){ + size_t B = Z(); //calculate the number of bands + size_t XY = X() * Y(); //calculate the number of pixels in a band + size_t XYb = XY * sizeof(T); //calculate the size of a band in bytes + + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file + file.seekg(0, std::ios::beg); //move the pointer to the current file to the beginning + + T* len = (T*)malloc(XYb); //allocate space to store the vector length + memset(len, 0, XYb); //initialize the vector length to zero (0) + + T* band = (T*) malloc(XYb); //allocate space to store a band image + + for(size_t b = 0; b < B; b++){ + file.read((char*)band, XYb); + for(size_t xy = 0; xy < XY; xy++){ + if(mask == NULL || mask[xy]){ + len[xy] += pow(band[xy], 2); //sum the squared value for each pixel value in the band + } + } + if(PROGRESS) progress = (double) (b+1) / (double)B * 50; + } + for(size_t xy = 0; xy < XY; xy++){ //for each pixel, calculate the square root + if(mask == NULL || mask[xy]){ + len[xy] += pow(band[xy], 2); //sum the squared value for each pixel value in the band + } + } + file.seekg(0, std::ios::beg); //move the pointer to the current file to the beginning + for(size_t b = 0; b < B; b++){ + file.read((char*)band, XYb); + for(size_t xy = 0; xy < XY; xy++){ + if(mask == NULL || mask[xy]){ + band[xy] /= len[xy]; //divide the band by the vector length + } + } + out.write((char*)band, XYb); + if(PROGRESS) progress = (double) (b+1) / (double)B * 50 + 50; + } + + } + + bool select(std::string outfile, std::vector bandlist, unsigned char* mask = NULL, bool PROGRESS = false) { + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file + if (!out) { + std::cout << "ERROR opening output file: " << outfile << std::endl; + return false; + } + file.seekg(0, std::ios::beg); //move the pointer to the current file to the beginning + + size_t B = Z(); //calculate the number of bands + size_t XY = X() * Y(); //calculate the number of pixels in a band + size_t in_bytes = XY * sizeof(T); //calculate the size of a band in bytes + + T* in = (T*)malloc(in_bytes); //allocate space for the band image + size_t nb = bandlist.size(); //get the number of bands in the output image + for (size_t b = 0; b < nb; b++) { + band(in, bandlist[b]); //get the band associated with the given wavelength + out.write((char*)in, in_bytes); //write the band to the output file + if (PROGRESS) progress = (double)(b + 1) / (double)bandlist.size() * 100; + } + out.close(); + free(in); + return true; + } + + size_t readlines(T* dest, size_t start, size_t n){ + return hsi::read(dest, 0, start, 0, X(), n, Z()); + } + + size_t writeblock(std::ofstream* f, T* src, size_t n){ + auto t0 = std::chrono::high_resolution_clock::now(); + f->write((char*)src, n); + auto t1 = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(t1-t0).count(); + } + + /// Convert this BSQ file to a BIL + bool bil(std::string outname, bool PROGRESS = false, bool VERBOSE = false, bool OPTIMIZATION = true){ + + const size_t buffers = 4; //number of buffers required for this algorithm + + size_t mem_per_batch = binary::buffer_size / buffers; //calculate the maximum memory available for a batch + + size_t slice_bytes = X() * Z() * sizeof(T); //number of bytes in an input batch slice (Y-slice in this case) + size_t max_slices_per_batch = mem_per_batch / slice_bytes; //maximum number of slices we can process in one batch given memory constraints + + std::cout<<"maximum memory available for processing: "<<(double)binary::buffer_size/(double)1000000<<" MB"<(Y(), max_slices_per_batch); //start with the maximum number of slices that can be stored (may be the entire data set) + + std::ofstream target(outname.c_str(), std::ios::binary); //open an output file for writing + //initialize with buffer 0 (used for double buffering) + size_t y_load = 0; + size_t y_proc = 0; + std::future rthread; + std::future wthread; //create asynchronous threads for reading and writing + + std::chrono::high_resolution_clock::time_point t_start, pt_start; //high-resolution timers + std::chrono::high_resolution_clock::time_point t_end, pt_end; + size_t t_batch; //number of milliseconds to process a batch + size_t t_total = 0; //total time for operation + size_t pt_total = 0; //total time spent processing data + size_t rt_total = 0; //total time spent reading data + size_t wt_total = 0; + size_t dr = 0; + + rt_total += readlines(src[0], 0, N[0]); //read the first batch into the 0 source buffer + y_load += N[0]; //increment the loaded slice counter + int b = 1; //initialize the double buffer to 0 + while(y_proc < Y()){ //while there are still slices to be processed + t_start = std::chrono::high_resolution_clock::now(); //start the timer for this batch + if(y_load < Y()){ //if there are still slices to be loaded, load them + //if(y_proc > 0){ + + + //} + if(y_load + N[b] > Y()) N[b] = Y() - y_load; //if the next batch would process more than the total slices, adjust the batch size + rthread = std::async(std::launch::async, &stim::bsq::readlines, this, src[b], y_load, N[b]); + //rt_total += rthread.get(); + y_load += N[b]; //increment the number of loaded slices + } + + b = !b; //swap the double-buffer + pt_total += binary::permute(dst[b], src[b], X(), N[b], Z(), 0, 2, 1); //permute the batch to a BIL file + wt_total += writeblock(&target, dst[b], N[b] * slice_bytes); //write the permuted data to the output file + y_proc += N[b]; //increment the counter of processed pixels + if(PROGRESS) progress = (double)( y_proc + 1 ) / Y() * 100; //increment the progress counter based on the number of processed pixels + if(y_proc < Y()) rt_total += rthread.get(); //if a new batch was set to load, make sure it loads after calculations + t_end = std::chrono::high_resolution_clock::now(); + t_batch = std::chrono::duration_cast(t_end-t_start).count(); + t_total += t_batch; + if(OPTIMIZATION) + N[b] = O.update(N[!b] * slice_bytes, t_batch, binary::data_rate, VERBOSE); //set the batch size based on optimization + //binary::data_rate = dr; + //std::cout<<"New N = "<::buffer_size / buffers; //calculate the maximum memory available for a batch + + size_t slice_bytes = X() * Z() * sizeof(T); //number of bytes in an input batch slice (Y-slice in this case) + size_t max_slices_per_batch = mem_per_batch / slice_bytes; //maximum number of slices we can process in one batch given memory constraints + + std::cout<<"maximum memory available for processing: "<<(double)binary::buffer_size/(double)1000000<<" MB"<(Y(), max_slices_per_batch); //start with the maximum number of slices that can be stored (may be the entire data set) + + std::ofstream target(outname.c_str(), std::ios::binary); //open an output file for writing + //initialize with buffer 0 (used for double buffering) + size_t y_load = 0; + size_t y_proc = 0; + std::future rthread; + std::future wthread; //create asynchronous threads for reading and writing + + std::chrono::high_resolution_clock::time_point t_start, pt_start; //high-resolution timers + std::chrono::high_resolution_clock::time_point t_end, pt_end; + size_t t_batch; //number of milliseconds to process a batch + size_t t_total = 0; //total time for operation + size_t pt_total = 0; //total time spent processing data + size_t rt_total = 0; //total time spent reading data + size_t wt_total = 0; + size_t dr = 0; + + rt_total += readlines(src[0], 0, N[0]); //read the first batch into the 0 source buffer + y_load += N[0]; //increment the loaded slice counter + int b = 1; //initialize the double buffer to 0 + while(y_proc < Y()){ //while there are still slices to be processed + t_start = std::chrono::high_resolution_clock::now(); //start the timer for this batch + if(y_load < Y()){ //if there are still slices to be loaded, load them + //if(y_proc > 0){ + + + //} + if(y_load + N[b] > Y()) N[b] = Y() - y_load; //if the next batch would process more than the total slices, adjust the batch size + rthread = std::async(std::launch::async, &stim::bsq::readlines, this, src[b], y_load, N[b]); + //rt_total += rthread.get(); + y_load += N[b]; //increment the number of loaded slices + } + + b = !b; //swap the double-buffer + pt_total += binary::permute(dst[b], src[b], X(), N[b], Z(), 2, 0, 1); //permute the batch to a BIL file + wt_total += writeblock(&target, dst[b], N[b] * slice_bytes); //write the permuted data to the output file + y_proc += N[b]; //increment the counter of processed pixels + if(PROGRESS) progress = (double)( y_proc + 1 ) / Y() * 100; //increment the progress counter based on the number of processed pixels + if(y_proc < Y()) rt_total += rthread.get(); //if a new batch was set to load, make sure it loads after calculations + t_end = std::chrono::high_resolution_clock::now(); + t_batch = std::chrono::duration_cast(t_end-t_start).count(); + t_total += t_batch; + if(OPTIMIZATION) + N[b] = O.update(N[!b] * slice_bytes, t_batch, binary::data_rate, VERBOSE); //set the batch size based on optimization + //binary::data_rate = dr; + //std::cout<<"New N = "< w[n-1] || rb >w[n-1]){ + if (lb < w[0]) { + std::cout << "bsq::area ERROR - left bound " << lb << " is below the minimum available wavelength " << w[0] << std::endl; + } + if (rb < w[0]) { + std::cout << "bsq::area ERROR - right bound " << rb << " is below the minimum available wavelength " << w[0] << std::endl; + } + if (lb > w[n - 1]) { + std::cout << "bsq::area ERROR - left bound " << lb << " is above the maximum available wavelength " << w[n - 1] << std::endl; + } + if (rb > w[n - 1]) { + std::cout << "bsq::area ERROR - right bound " << rb << " is above the maximum available wavelength " << w[0] << std::endl; + } + return false; + } + //to make sure right bound is bigger than left bound + else if(lb > rb){ + std::cout << "bsq::area ERROR - right bound " << rb << " should be larger than left bound " << lb << std::endl; + return false; + } + + //find the indices of the left and right baseline points + while (lab >= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); //get the band images for the left and right baseline points + band(rp, rb); + + // calculate the average value of the indexed region + memset(result, 0, S); //initialize the integral to zero (0) + + //integrate the region between the specified bands and the closest indexed band + // this integrates the "tails" of the spectrum that lie outside the main indexed region + baseline_band(lb, rb, lp, rp, rab, cur2); //calculate the image for the right-most band in the integral + baseline_band(lb, rb, lp, rp, w[bi], cur); //calculate the image for the right-most indexed band + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + + //integrate the main indexed region + ai++; + for(unsigned long long i = ai; i <= bi ;i++) //for each band in the integral + { + baseline_band(lb, rb, lp, rp, w[ai], cur2); //get the baselined band + for(unsigned long long j = 0; j < XY; j++){ //for each pixel in that band + result[j] += (T)((w[ai] - w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 2.0); + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the ratio of two baseline-corrected peaks. The result is stored in a pre-allocated array. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool ph_to_ph(T* result, double lb1, double rb1, double pos1, double lb2, double rb2, double pos2, unsigned char* mask = NULL){ + + size_t XYbytes = X() * Y() * sizeof(T); //calculate the size of the band image (in bytes) + + T* p1 = (T*)malloc(XYbytes); //allocate space for both bands in the ratio + T* p2 = (T*)malloc(XYbytes); + + memset(result, 0, XYbytes); //initialize the ratio to zero + //get the two peak band + height(lb1, rb1, pos1, p1); + height(lb2, rb2, pos2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]){ + result[i] = p1[i] / p2[i]; + } + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between a peak area and peak height. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_ph(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double pos, unsigned char* mask = NULL){ + + size_t bytes = X() * Y() * sizeof(T); + T* p1 = (T*)malloc(bytes); //allocate space for both ratio components + T* p2 = (T*)malloc(bytes); + + memset(result, 0, bytes); //initialize the ratio to zero + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + height(lb2, rb2, pos, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]) + result[i] = p1[i] / p2[i]; + } + + free(p1); + free(p2); + return true; + } + + /// Compute the ratio between two peak areas. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param lab1 is the label value for the left bound (start of the integration) of the first peak (numerator) + /// @param rab1 is the label value for the right bound (end of the integration) of the first peak (numerator) + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param lab2 is the label value for the left bound (start of the integration) of the second peak (denominator) + /// @param rab2 is the label value for the right bound (end of the integration) of the second peak (denominator) + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool pa_to_pa(T* result, double lb1, double rb1, double lab1, double rab1, + double lb2, double rb2, double lab2, double rab2, unsigned char* mask = NULL){ + + size_t bytes = X() * Y() * sizeof(T); + T* p1 = (T*)malloc(bytes); //allocate space for each of the operands + T* p2 = (T*)malloc(bytes); + + memset(result, 0, bytes); //initialize the ratio result to zero (0) + //get the area and the peak band + area(lb1, rb1, lab1, rab1, p1); + area(lb2, rb2, lab2, rab2, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]) //if the pixel is masked + result[i] = p1[i] / p2[i]; //calculate the ratio + } + + free(p1); + free(p2); + return true; + } + + /// Compute the definite integral of a baseline corrected peak weighted by the corresponding wavelength + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the definite integral + /// @param rab is the label for the end of the definite integral + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool x_area(double lb, double rb, double lab, double rab, T* result){ + T* lp; //left band pointer + T* rp; //right band pointer + T* cur; //current band 1 + T* cur2; //current band 2 + + unsigned long long XY = X() * Y(); + unsigned long long S = XY * sizeof(T); + + lp = (T*) malloc(S); //memory allocation + rp = (T*) malloc(S); + cur = (T*) malloc(S); + cur2 = (T*) malloc(S); + + //find the wavelenght position in the whole band + unsigned long long n = w.size(); + unsigned long long ai = 0; //left bound position + unsigned long long bi = n - 1; //right bound position + + //to make sure the left and the right bound are in the bandwidth + if (lb < w[0] || rb < w[0] || lb > w[n-1] || rb >w[n-1]){ + std::cout<<"ERROR: left bound or right bound out of bandwidth"< rb){ + std::cout<<"ERROR: right bound should be bigger than left bound"<= w[ai]){ + ai++; + } + while (rab <= w[bi]){ + bi--; + } + + band(lp, lb); + band(rp, rb); + + memset(result, (char)0, S); //initialize the integral to zero (0) + + //calculate the beginning and the ending part + baseline_band(lb, rb, lp, rp, rab, cur2); //ending part + baseline_band(lb, rb, lp, rp, w[bi], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((rab - w[bi]) * (rab + w[bi]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + baseline_band(lb, rb, lp, rp, lab, cur2); //beginnning part + baseline_band(lb, rb, lp, rp, w[ai], cur); + for(unsigned long long j = 0; j < XY; j++){ + result[j] += (T)((w[ai] - lab) * (w[ai] + lab) * ((double)cur[j] + (double)cur2[j]) / 4.0); + } + + //calculate f(x) times x + ai++; + for(unsigned long long i = ai; i <= bi ;i++){ + baseline_band(lb, rb, lp, rp, w[ai], cur2); + for(unsigned long long j = 0; j < XY; j++){ + T v = (T)((w[ai] - w[ai-1]) * (w[ai] + w[ai-1]) * ((double)cur[j] + (double)cur2[j]) / 4.0); + result[j] += v; + } + std::swap(cur,cur2); //swap the band pointers + } + + free(lp); + free(rp); + free(cur); + free(cur2); + return true; + } + + /// Compute the centroid of a baseline corrected peak. + /// Note that the values for the centroid can be outside of [lab, rab] if the spectrum goes negative. + + /// @param lb is the label value for the left baseline point + /// @param rb is the label value for the right baseline point + /// @param lab is the label for the start of the peak + /// @param rab is the label for the end of the peak + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool centroid(T* result, double lb, double rb, double lab, double rab, unsigned char* mask = NULL){ + size_t bytes = X() * Y() * sizeof(T); //calculate the number of bytes in a band image + T* p1 = (T*)malloc(X() * Y() * sizeof(T)); //allocate space for both operands + T* p2 = (T*)malloc(X() * Y() * sizeof(T)); + + memset(result, 0, bytes); //initialize the ratio result to zero (0) + //get the area and the peak band + x_area(lb, rb, lab, rab, p1); + area(lb, rb, lab, rab, p2); + //calculate the ratio in result + for(unsigned long long i = 0; i < X() * Y(); i++){ + if(mask == NULL || mask[i]){ + result[i] = p1[i] / p2[i]; + } + } + + free(p1); + free(p2); + return true; + } + + /// Create a mask based on a given band and threshold value. + + /// All pixels in the + /// specified band greater than the threshold are true and all pixels less than the threshold are false. + /// @param mask_band is the band used to specify the mask + /// @param threshold is the threshold used to determine if the mask value is true or false + /// @param p is a pointer to a pre-allocated array at least X * Y in size + bool build_mask(unsigned char* out_mask, double mask_band, double lower, double upper, unsigned char* mask = NULL, bool PROGRESS = false){ + memset(out_mask, 0, X() * Y()); //initialize the mask to zero + + T* temp = (T*)malloc(X() * Y() * sizeof(T)); //allocate memory for the certain band + band(temp, mask_band); + + for (unsigned long long i = 0; i < X() * Y(); i++) { + if(mask == NULL || mask[i] != 0){ + if(temp[i] > lower && temp[i] < upper){ + out_mask[i] = 255; + } + else + out_mask[i] = 0; + } + + if(PROGRESS) progress = (double) (i+1) / (X() * Y()) * 100; + } + + free(temp); + return true; + + } + + /// Apply a mask file to the BSQ image, setting all values outside the mask to zero. + + /// @param outfile is the name of the masked output file + /// @param p is a pointer to memory of size X * Y, where p(i) = 0 for pixels that will be set to zero. + bool apply_mask(std::string outfile, unsigned char* p, bool PROGRESS = false){ + + std::ofstream target(outfile.c_str(), std::ios::binary); + + unsigned long long XY = X() * Y(); //calculate number of a band + unsigned long long L = XY * sizeof(T); + + T * temp = (T*)malloc(L); + + for (unsigned long long i = 0; i < Z(); i++) //for each spectral bin + { + band_index(temp, i); //get the specified band (by index) + for ( unsigned long long j = 0; j < XY; j++) // for each pixel + { + if(p[j] == 0){ //if the mask is 0 at that pixel + temp[j] = 0; //set temp to zero + } + else{ + continue; + } + } + target.write(reinterpret_cast(temp), L); //write the XY slice at that band to disk + if(PROGRESS) progress = (double)(i + 1) / (double)Z() * 100; + } + target.close(); + free(temp); + return true; + } + + /// Copies all spectra corresponding to nonzero values of a mask into a pre-allocated matrix of size (B x P) + /// where P is the number of masked pixels and B is the number of bands. The allocated memory can be accessed + /// using the following indexing: i = p*B + b + /// @param matrix is the destination for the pixel data + /// @param mask is the mask + bool sift(T* matrix, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long XY = X() * Y(); //Number of XY pixels + unsigned long long L = XY * sizeof(T); //size of XY plane (in bytes) + + //calculate the number of pixels in the mask + //unsigned long long P = nnz(mask); + + T* band_image = (T*) malloc( XY * sizeof(T)); //allocate space for a single band + + unsigned long long i; //pixel index into the sifted array + for(unsigned long long b = 0; b < Z(); b++){ //for each band in the data set + band_index(band_image, b); //retrieve an image of that band + + i = 0; + for(unsigned long long xy = 0; xy < XY; xy++){ + if(mask == NULL || mask[xy] != 0){ //if the pixel is valid + matrix[i*Z() + b] = band_image[xy]; //copy it to the appropriate point in the values[] array + i++; + } + if(PROGRESS) progress = (double)(b * XY + xy+1) / (double)(XY * Z()) * 100; + } + } + + return true; + } + + /// Saves to disk only those spectra corresponding to mask values != 0 + /// @param outfile is the name of the sifted ENVI file to be written to disk + /// @param unsigned char* p is the mask file used for sifting + bool sift(std::string outfile, unsigned char* p, bool PROGRESS = false){ + std::ofstream target(outfile.c_str(), std::ios::binary); + // open a band (XY plane) + unsigned long long XY = X() * Y(); //Number of XY pixels + unsigned long long L = XY * sizeof(T); //size of XY pixels + unsigned long long B = Z(); + + T * temp = (T*)malloc(L); //allocate memory for a band + T * temp_vox = (T*)malloc(sizeof(T)); //allocate memory for one voxel + + for (unsigned long long i = 0; i < B; i++) //for each spectral bin + { + band_index(temp, i); //get the specified band (XY sheet by index) + for (unsigned long long j = 0; j < XY; j++) // for each pixel + { + if (p[j] != 0){ //if the mask is != 0 at that pixel + temp_vox[0] = temp[j]; + target.write(reinterpret_cast(temp_vox), sizeof(T)); //write the XY slice at that band to disk + } + else{ + continue; + } + } + if(PROGRESS) progress = (double)(i+1)/ B * 100; + } + target.close(); + free(temp); + + progress = 100; + + return true; + } + + /// Generates a spectral image from a matrix of spectral values in lexicographic order and a mask + bool unsift(std::string outfile, unsigned char* p, unsigned long long samples, unsigned long long lines, bool PROGRESS = false){ + + //create a binary output stream + std::ofstream target(outfile.c_str(), std::ios::binary); + + //make sure that there's only one line + if(Y() != 1){ + std::cout<<"ERROR in stim::bsq::sift() - number of lines does not equal 1"<(unsifted), sizeof(T) * XY); + } + + //std::cout<<"unsifted"< band_values(count); //create an STD vector of band values + + //this loops goes through each band in B (Z()) + // masked (or valid) pixels from that band are averaged and the average is stored in p + size_t k; + for (size_t i = 0; i < Z(); i++){ //for each band + band_index(temp, i); //get the band image and store it in temp + k = 0; //initialize the band_value index to zero + for (size_t j = 0; j < XY; j++){ //loop through temp, averaging valid pixels + if (mask == NULL || mask[j] != 0){ + band_values[k] = temp[j]; //store the value in the band_values array + k++; //increment the band_values index + } + } + std::sort(band_values.begin(), band_values.end()); //sort all of the values in the band + m[i] = band_values[ count/2 ]; //store the center value in the array + if(PROGRESS) progress = (double)(i+1) / Z() * 100; //update the progress counter + } + free(temp); + return true; + } + + /// Crop a region of the image and save it to a new file. + + /// @param outfile is the file name for the new cropped image + /// @param x0 is the lower-left x pixel coordinate to be included in the cropped image + /// @param y0 is the lower-left y pixel coordinate to be included in the cropped image + /// @param x1 is the upper-right x pixel coordinate to be included in the cropped image + /// @param y1 is the upper-right y pixel coordinate to be included in the cropped image + bool crop(std::string outfile, unsigned long long x0, + unsigned long long y0, + unsigned long long x1, + unsigned long long y1, + unsigned long long b0, + unsigned long long b1, + bool PROGRESS = false){ + + //calculate the new number of samples, lines, and bands + unsigned long long samples = x1 - x0 + 1; + unsigned long long lines = y1 - y0 + 1; + unsigned long long bands = b1 - b0 + 1; + + //calculate the size of a single band + unsigned long long L = samples * lines * sizeof(T); + + //allocate space for a single band + T* temp = (T*)malloc(L); + + //create an output stream to store the output file + std::ofstream out(outfile.c_str(), std::ios::binary); + + //calculate the distance required to jump from the end of one band to the beginning of another + unsigned long long jumpb = X() * (Y() - lines) * sizeof(T); + + //calculate the distance required to jump from the end of one line to the beginning of another + unsigned long long jumpl = (X() - samples) * sizeof(T); + + //seek to the start of the cropped region in the input file + file.seekg( (b0 * X() * Y() + y0 * X() + x0) * sizeof(T), std::ios::beg); + + //for each band + for (unsigned long long z = b0; z <= b1; z++) + { + //std::cout<(temp), L); //write slice data into target file + file.seekg(jumpb, std::ios::cur); + } + free(temp); + + return true; + } + + ///Crop out several subimages and assemble a new image from these concatenated subimages + + /// @param outfile is the file name for the output image + /// @param sx is the width of each subimage + /// @param sy is the height of each subimage + /// @mask is the mask used to define subimage positions extracted from the input file + void subimages(std::string outfile, size_t sx, size_t sy, unsigned char* mask, bool PROGRESS = false){ + + size_t N = nnz(mask); //get the number of subimages + T* dst = (T*) malloc(N * sx * sy * sizeof(T)); //allocate space for a single band of the output image + memset(dst, 0, N*sx*sy*sizeof(T)); //initialize the band image to zero + + std::ofstream out(outfile, std::ios::binary); //open a file for writing + + T* src = (T*) malloc(X() * Y() * sizeof(T)); + + for(size_t b = 0; b < Z(); b++){ //for each band + band_index(src, b); //load the band image + size_t i = 0; //create an image index and initialize it to zero + size_t n = 0; + while(n < N){ //for each subimage + if(mask[i]){ //if the pixel is masked, copy the surrounding pixels into the destination band + size_t yi = i / X(); //determine the y position of the current pixel + size_t xi = i - yi * X(); //determine the x position of the current pixel + if( xi > sx/2 && xi < X() - sx/2 && //if the subimage is completely within the bounds of the original image + yi > sy/2 && yi < Y() - sy/2){ + size_t cx = xi - sx/2; //calculate the corner position for the subimage + size_t cy = yi - sy/2; + for(size_t syi = 0; syi < sy; syi++){ //for each line in the subimage + size_t src_i = (cy + syi) * X() + cx; + //size_t dst_i = syi * (N * sx) + n * sx; + size_t dst_i = (n * sy + syi) * sx; + memcpy(&dst[dst_i], &src[src_i], sx * sizeof(T)); //copy one line from the subimage to the destination image + } + n++; + } + } + i++; + if(PROGRESS) progress = (double)( (n+1) * (b+1) ) / (N * Z()) * 100; + }//end while n + out.write((const char*)dst, N * sx * sy * sizeof(T)); //write the band to memory + } + free(dst); //free memory + free(src); + } + + /// Remove a list of bands from the ENVI file + + /// @param outfile is the file name for the output hyperspectral image (with trimmed bands) + /// @param b is an array of bands to be eliminated + void trim(std::string outfile, std::vector band_array, bool PROGRESS = false){ + + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of the input file + + size_t XY = X() * Y(); //calculate the number of elements in a band + size_t XYb = XY * sizeof(T); //calculate the number of bytes in a band + T* temp = (T*)malloc(XYb); //allocate space to store a band + + size_t i = 0; //store the first index into the band array + + for(size_t b = 0; b < Z(); b++){ //for each band + if(b != band_array[i]){ //if this band is not trimmed + file.read((char*)temp, XYb); //read the band + out.write((char*)temp, XYb); //output the band + } + else{ + file.seekg(XYb, std::ios::cur); //otherwise, skip the band + i++; + } + if(PROGRESS) progress = (double)(b+1) / (double) Z() * 100; + } + free(temp); //free the scratch space for the band + } + + /// Combine two BSQ images along the Y axis + + /// @param outfile is the combined file to be output + /// @param infile is the input file stream for the image to combine with this one + /// @param Sx is the size of the second image along X + /// @param Sy is the size of the second image along Y + /// @param offset is a shift (negative or positive) in the combined image to the left or right + void combine(std::string outfile, bsq* C, long long xp, long long yp, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + + size_t S[2]; //size of the output band image + size_t p0[2]; //position of the current image in the output + size_t p1[2]; //position of the source image in the output + + hsi::calc_combined_size(xp, yp, C->X(), C->Y(), S[0], S[1], p0[0], p0[1], p1[0], p1[1]); //calculate the image placement parameters + + size_t line_bytes = X() * sizeof(T); + size_t band_bytes = X() * Y() * sizeof(T); + T* cur = (T*)malloc(X() * Y() * sizeof(T)); //allocate space for a band of the current image + + size_t line_src_bytes = C->X() * sizeof(T); + size_t band_src_bytes = C->X() * C->Y() * sizeof(T); + T* src = (T*)malloc(C->X() * C->Y() * sizeof(T)); //allocate space for a band of the source image + + size_t line_dst_bytes = S[0] * sizeof(T); + size_t band_dst_bytes = S[0] * S[1] * sizeof(T); + T* dst = (T*)malloc(band_dst_bytes); //allocate space for a band of the destination image + memset(dst, 0, band_dst_bytes); //set all values to zero (0) in the destination image + + for(size_t b = 0; b < Z(); b++){ //for each band in both images + file.read((char*)cur, band_bytes); //read a band from the current image + C->file.read((char*)src, band_src_bytes); //read a band from the source image + for(size_t y = 0; y < Y(); y++) + memcpy( &dst[ (p0[1]+y) * S[0] + p0[0] ], &cur[ y * X() ], line_bytes); //copy the line from the current to the destination image + //memset( &dst[ (p0[1]+y) * S[0] + p0[0] ], 0, line_dst_bytes); + for(size_t y = 0; y < C->Y(); y++) + memcpy( &dst[ (p1[1]+y) * S[0] + p1[0] ], &src[ y * C->X() ], line_src_bytes); //copy the line from the source to the destination image + out.write((char*)dst, band_dst_bytes); //write the combined image to an output file + if(PROGRESS) progress = (double)(b + 1) / (double) Z() * 100; + } + out.close(); + } + + /// Append an image to this one along the band dimension + void append(std::string outfile, bsq* C, bool PROGRESS = false) { + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + file.seekg(0, std::ios::beg); //move to the beginning of both files + C->file.seekg(0, std::ios::beg); + + if (PROGRESS) progress = 0; + out << file.rdbuf(); //copy the data from this ENVI file + if (PROGRESS) progress = (double)(Z() + 1) / (double)(Z() + C->Z()) * 100; + out << C->file.rdbuf(); //copy the data from the appending file + if (PROGRESS) progress = 100; + out.close(); + } + + /// Convolve the given band range with a kernel specified by a vector of coefficients. + + /// @param outfile is an already open stream to the output file + /// @param C is an array of coefficients + /// @param start is the band to start processing (the first coefficient starts here) + /// @param nbands is the number of bands to process + /// @param center is the index for the center coefficient for the kernel (used to set the wavelengths in the output file) + + void convolve(std::ofstream& out, std::vector C, size_t start, size_t end, unsigned char* mask = NULL, bool PROGRESS = false){ + size_t nbands = end - start + 1; + size_t XY = X() * Y(); //calculate the number of values in a band + size_t XYb = XY * sizeof(T); //calculate the size of a band (frame) in bytes + + file.seekg(XYb * start, std::ios::beg); //move to the beginning of the 'start' band + + size_t nframes = C.size(); //get the number of bands that the kernel spans + std::deque frame(nframes, NULL); //create an array to store pointers to each frame + for(size_t f = 0; f < nframes; f++){ //for each frame + frame[f] = (T*)malloc(XYb); //allocate space for the frame + file.read((char*)frame[f], XYb); //load the frame + } + + T* outband = (T*)malloc(XYb); //allocate space for the output band + + //Implementation: In order to minimize reads from secondary storage, each band is only loaded once into the 'frame' deque. + // When a new band is loaded, the last band is popped, a new frame is copied to the pointer, and it is + // re-inserted into the deque. + for(size_t b = 0; b < nbands; b++){ //for each band + memset(outband, 0, XYb); //set the output band to zero (0) + size_t c, xy; + double coeff; + for(c = 0; c < nframes; c++){ //for each frame (corresponding to each coefficient) + coeff = C[c]; + for(xy = 0; xy < XY; xy++){ //for each pixel + if(mask == NULL || mask[xy]){ + outband[xy] += (T)(coeff * frame[c][xy]); //calculate the contribution of the current frame (scaled by the corresponding coefficient) + } + } + } + out.write((char*)outband, XYb); //output the band + file.read((char*)frame[0], XYb); //read the next band + frame.push_back(frame.front()); //put the first element in the back + frame.pop_front(); //pop the first element + if(PROGRESS) progress = (double)(b+1) / (double)nbands * 100; + } + } + + /// Performs a single convolution and saves it to an output file + + /// @param outfile is the convolved file to be output + /// @param C is an array of coefficients + /// @param start is the band to start processing (the first coefficient starts here) + /// @param nbands is the number of bands to process + void convolve(std::string outfile, std::vector C, size_t start, size_t end, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + convolve(out, C, start, end, mask, PROGRESS); //start the convolution + out.close(); + } + + /// Performs a set of convolutions and chains the results together in a single file + + /// @param outfile is the convolved file to be output + /// @param C is an array containing an array of coefficients for each kernel + /// @param start is the list of start bands for each kernel + /// @param end is the list of end bands for each kernel + void convolve(std::string outfile, std::vector< std::vector > C, std::vector start, std::vector end, unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + + size_t K = C.size(); //get the number of kernels + for(size_t k = 0; k < K; k++){ + size_t b0 = start[k]; //calculate the range of the convolution + size_t b1 = end[k]; + convolve(out, C[k], b0, b1, mask, PROGRESS); //perform the convolution with the current kernel in the given range + } + out.close(); + } + + /// Approximate the spectral derivative of the image + + void deriv(std::string outfile, size_t d, size_t order, const std::vector w = std::vector(), unsigned char* mask = NULL, bool PROGRESS = false){ + std::ofstream out(outfile.c_str(), std::ios::binary); //open the output file for writing + + size_t XY = X() * Y(); //calculate the number of values in a band + size_t XYb = XY * sizeof(T); //calculate the size of a band (frame) in bytes + size_t B = Z(); + file.seekg(0, std::ios::beg); //move to the beginning of the file + + size_t N = order + d; //approximating a derivative requires order + d samples + std::deque frame(N, NULL); //create an array to store pointers to each frame + for(size_t f = 0; f < N; f++){ //for each frame + frame[f] = (T*)malloc(XYb); //allocate space for the frame + file.read((char*)frame[f], XYb); //load the frame + } + + T* outband = (T*)malloc(XYb); //allocate space for the output band + + //Implementation: In order to minimize reads from secondary storage, each band is only loaded once into the 'frame' deque. + // When a new band is loaded, the last band is popped, a new frame is copied to the pointer, and it is + // re-inserted into the deque. + size_t mid = (size_t)(N / 2); //calculate the mid point of the kernel + size_t iw; //index to the first wavelength used to evaluate the derivative at this band + for(size_t b = 0; b < B; b++){ //for each band + if(b < mid) //calculate the first wavelength used to evaluate the derivative at this band + iw = 0; + else if(b > B - (N - mid + 1)) + iw = B - N; + else{ + iw = b - mid; + file.read((char*)frame[0], XYb); //read the next band + frame.push_back(frame.front()); //put the first element in the back + frame.pop_front(); //pop the first element + } + std::vector w_pts(w.begin() + iw, w.begin() + iw + N); //get the wavelengths corresponding to each sample + std::vector C = diff_coefficients(w[b], w_pts, d); //get the optimal sample weights + + memset(outband, 0, XYb); //set the output band to zero (0) + for(size_t c = 0; c < N; c++){ //for each frame (corresponding to each coefficient) + for(size_t xy = 0; xy < XY; xy++){ //for each pixel + if(mask == NULL || mask[xy]){ + outband[xy] += (T)(C[c] * frame[c][xy]); //calculate the contribution of the current frame (scaled by the corresponding coefficient) + } + } + } + out.write((char*)outband, XYb); //output the band + if(PROGRESS) progress = (double)(b+1) / (double)B * 100; + } + + } //end deriv + + bool multiply(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long B = Z(); //calculate the number of bands + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + T * c; //pointer to the current image + c = (T*)malloc( S ); //allocate memory for the band image + + for(unsigned long long j = 0; j < B; j++){ //for each band + band_index(c, j); //load the current band + for(unsigned long long i = 0; i < XY; i++){ //for each pixel + if(mask == NULL || mask[i]) //if the pixel is masked + c[i] *= (T)v; //perform the multiplication + } + target.write(reinterpret_cast(c), S); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / B * 100; //update the progress + } + + free(c); //free the band + target.close(); //close the output file + return true; + } + + bool add(std::string outname, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + unsigned long long B = Z(); //calculate the number of bands + unsigned long long XY = X() * Y(); //calculate the number of pixels in a band + unsigned long long S = XY * sizeof(T); //calculate the number of bytes in a band + + std::ofstream target(outname.c_str(), std::ios::binary); //open the target binary file + std::string headername = outname + ".hdr"; //the header file name + + T * c; //pointer to the current image + c = (T*)malloc( S ); //allocate memory for the band image + + for(unsigned long long j = 0; j < B; j++){ //for each band + band_index(c, j); //load the current band + for(unsigned long long i = 0; i < XY; i++){ //for each pixel + if(mask == NULL || mask[i]) //if the pixel is masked + c[i] += (T)v; //perform the multiplication + } + target.write(reinterpret_cast(c), S); //write normalized data into destination + + if(PROGRESS) progress = (double)(j+1) / B * 100; //update the progress + } + + free(c); //free the band + target.close(); //close the output file + return true; + } + + + /// Close the file. + bool close(){ + file.close(); + return true; + } + + }; +} + +#endif + + diff --git a/tira/envi/convert.cu b/tira/envi/convert.cu new file mode 100644 index 0000000..1f4ded2 --- /dev/null +++ b/tira/envi/convert.cu @@ -0,0 +1,27 @@ +//#include + +__global__ void kernel_permute(char* dest, char* src, size_t sx, size_t sy, size_t sz, size_t d0, size_t d1, size_t d2, size_t typesize){ + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + size_t zi = blockIdx.z * blockDim.z + threadIdx.z; + + if(xi >= sx || yi >= sy || zi >= sz) return; //return if we are outside the grid + + size_t d[3] = {d0, d1, d2}; + size_t s[3] = {sx, sy, sz}; + size_t p[3] = {xi, yi, zi}; + + size_t si = typesize * (zi * sx * sy + yi * sx + xi); + size_t di = typesize * (p[d[2]] * s[d[0]] * s[d[1]] + p[d[1]] * s[d[0]] + p[d[0]]); + + for(int i = 0; i < typesize; i++) + dest[di+i] = src[si + i]; +} + +void gpu_permute(char* dest, char* src, size_t sx, size_t sy, size_t sz, size_t d0, size_t d1, size_t d2, size_t typesize){ + + int threads_per_block = 1024; + dim3 threads(sqrt(threads_per_block), sqrt(threads_per_block), 1); + dim3 blocks(sx/threads.x + 1, sy/threads.y + 1, sz/threads.z +1); + kernel_permute<<>>(dest, src, sx, sy, sz, d0, d1, d2, typesize); +} diff --git a/tira/envi/envi.h b/tira/envi/envi.h new file mode 100644 index 0000000..aafa3ea --- /dev/null +++ b/tira/envi/envi.h @@ -0,0 +1,2115 @@ +#ifndef STIM_ENVI_H +#define STIM_ENVI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include "../image/image.h" + +namespace stim{ + +/** This class implements reading of ENVI hyperspectral files. These files can be stored in multiple orientations + (including BSQ, BIP, and BIL) in order to optimize streaming speed depending on applications. Basic ENVI + files are stored on disk as a large binary file with a corresponding header. Code for reading and processing + ENVI header files is in the envi_header class. +*/ +class envi{ + + void* file; //void pointer to the relevant file reader (bip, bsq, or bil - with appropriate data type) + std::string fname; //file name used for repeated opening and closing + + //allocate sufficient space for a spectrum based on the data type and number of bands + void* alloc_array(size_t len){ + switch(header.data_type){ + case envi_header::int8: + return malloc(len); + case envi_header::int16: + case envi_header::uint16: + return malloc(2 * len); + case envi_header::int32: + case envi_header::uint32: + case envi_header::float32: + return malloc(4 * len); + case envi_header::int64: + case envi_header::uint64: + case envi_header::float64: + case envi_header::complex32: + return malloc(8 * len); + case envi_header::complex64: + return malloc(16 * len); + default: + std::cout<<"ERROR stim::envi data type not recognized for spectral allocation"< + inline void cast(DST* dst, SRC* src){ + (*dst) = (DST)(*src); + } + + //cast an array from type SRC to type DEST + template + inline void cast(DST* dst, SRC* src, size_t len){ + for(size_t i = 0; i < len; i++) + cast(&dst[i], &src[i]); + } + +public: + envi_header header; + + + /// Default constructor + envi(){ + file = NULL; //set the file pointer to NULL + } + + envi(std::string filename, std::string headername) : envi(){ + header.load(headername); + + fname = filename; //save the filename + + allocate(); + } + //used to test if the current ENVI file is valid + operator bool(){ + if (header.interleave == envi_header::BSQ) { //if the infile is bsq file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->is_open(); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->is_open(); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIL) { //if the infile is bil file + if (header.data_type == envi_header::float32) + return ((bil*)file)->is_open(); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->is_open(); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIP) { //if the infile is bip file + if (header.data_type == envi_header::float32) + return ((bip*)file)->is_open(); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->is_open(); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + else { + std::cout << "ERROR: unidentified file type" << std::endl; + exit(1); + } + return false; + } + + //test to determine if the specified file is an ENVI file + static bool is_envi(std::string fname, std::string hname = ""){ + stim::filename data_file(fname); + stim::filename header_file; + if(hname == ""){ //if the header isn't provided + header_file = data_file; //assume that it's the same name as the data file, with a .hdr extension + header_file = header_file.extension("hdr"); + } + else header_file = hname; //otherwise load the passed header + + stim::envi_header H; + if(H.load(header_file) == false) //load the header file, if it doesn't load return false + return false; + size_t targetBytes = H.data_bytes(); //get the number of bytes that SHOULD be in the data file + size_t bytes = stim::file_size(fname); + if (bytes != targetBytes) { + std::cout << "ERROR: File size mismatch. Based on the header, a " << targetBytes << " byte file was expected. The data file contains " << bytes << " bytes." << std::endl; + return false; //if the data doesn't match the header, return false + } + return true; //otherwise everything looks fine + + } + + + void* malloc_spectrum(){ + return alloc_array(header.bands); + } + + void* malloc_band(){ + return alloc_array(header.samples * header.lines); + } + + void set_buffer_frac(double memfrac = 0.5){ + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + if(header.data_type ==envi_header::float32) + ((bsq*)file)->set_buffer_frac(memfrac); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->set_buffer_frac(memfrac); + else + std::cout<<"ERROR: unidentified data type"<*)file)->set_buffer_frac(memfrac); + else if(header.data_type == envi_header::float64) + ((bil*)file)->set_buffer_frac(memfrac); + else + std::cout<<"ERROR: unidentified data type"<*)file)->set_buffer_frac(memfrac); + else if(header.data_type == envi_header::float64) + ((bip*)file)->set_buffer_frac(memfrac); + else + std::cout<<"ERROR: unidentified data type"<*)file)->set_buffer_raw(bytes); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->set_buffer_raw(bytes); + else + std::cout<<"ERROR: unidentified data type"<*)file)->set_buffer_raw(bytes); + else if(header.data_type == envi_header::float64) + ((bil*)file)->set_buffer_raw(bytes); + else + std::cout<<"ERROR: unidentified data type"<*)file)->set_buffer_raw(bytes); + else if(header.data_type == envi_header::float64) + ((bip*)file)->set_buffer_raw(bytes); + else + std::cout<<"ERROR: unidentified data type"<*)file)->reset_progress(); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->reset_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->reset_progress(); + else if(header.data_type == envi_header::float64) + ((bil*)file)->reset_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->reset_progress(); + else if(header.data_type == envi_header::float64) + ((bip*)file)->reset_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_progress(); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->get_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_progress(); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->get_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_progress(); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->get_progress(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_data_rate(); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->get_data_rate(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_data_rate(); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->get_data_rate(); + else + std::cout<<"ERROR: unidentified data type"<*)file)->get_data_rate(); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->get_data_rate(); + else + std::cout<<"ERROR: unidentified data type"<(); + else if(header.data_type == envi_header::float64) + file = new bsq(); + } + else if(header.interleave == envi_header::BIP){ + if(header.data_type ==envi_header::float32) + file = new bip(); + else if(header.data_type == envi_header::float64) + file = new bip(); + } + else if(header.interleave == envi_header::BIL){ + if(header.data_type ==envi_header::float32) + file = new bil(); + else if(header.data_type == envi_header::float64) + file = new bil(); + } + + } + + /// Open a previously opened ENVI file + bool open(stim::iotype io = stim::io_in){ + + //load the file + if(header.interleave == envi_header::BSQ) { //if the infile is bsq file + if(header.data_type == envi_header::float32) { + return ((bsq*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + else if(header.data_type == envi_header::float64) { + return ((bsq*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + else + { + std::cout << "ERROR: The specified data format (" << header.data_type << ") is not supported" << std::endl; + return false; + } + } + + else if(header.interleave == envi_header::BIL) { //if the infile is bil file + if(header.data_type == envi_header::float32) { + return ((bil*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + else if(header.data_type == envi_header::float64) { + return ((bil*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + { + std::cout << "ERROR: The specified data format (" << header.data_type << ") is not supported" << std::endl; + return false; + } + } + + else if(header.interleave == envi_header::BIP) { //if the infile is bip file + if(header.data_type == envi_header::float32) { + return ((bip*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + else if(header.data_type == envi_header::float64) { + return ((bip*)file)->open(fname, header.samples, header.lines, header.bands, header.header_offset, header.wavelength); + } + { + std::cout << "ERROR: The specified data format (" << header.data_type << ") is not supported" << std::endl; + return false; + } + } + + return true; + + + } + + /// Open an Agilent binary file as an ENVI stream + bool open_agilent(std::string filename){ + fname = filename; //store the file name + + //Open the file temporarily to get the header information + FILE* f = fopen(filename.c_str(), "r"); //open the binary file for reading + if(f == NULL) return false; //return false if no file is opened + + fseek(f, 9, SEEK_SET); //seek to the number of bands + short b; //allocate space for the number of bands + size_t nread = fread(&b, sizeof(short), 1, f); //read the number of bands + if(nread != 1){ + std::cout<<"Error reading band number from Agilent file."< threshold (preventing division by small numbers) + bool ratio(std::string outfile, double band, unsigned char* mask = NULL, bool PROGRESS = false){ + header.save(outfile + ".hdr"); + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + if(header.data_type ==envi_header::float32) + return ((bsq*)file)->ratio(outfile, band, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->ratio(outfile,band, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->ratio(outfile, band, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->ratio(outfile,band, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->ratio(outfile, band, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->ratio(outfile,band, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->normalize(outfile, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->normalize(outfile, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->normalize(outfile, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bil*)file)->normalize(outfile, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->normalize(outfile, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bip*)file)->normalize(outfile, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"< bandlist, unsigned char* MASK = NULL, bool PROGRESS = false) { + stim::envi_header new_header = header; //copy all of the data from the current header file to the new one + new_header.bands = bandlist.size(); //the number of bands in the new file is equal to the number of bands provided by the user + new_header.wavelength = bandlist; //the wavelength values in the output file are the same as those specified by the user + new_header.band_names.empty(); //no band names will be provided in the output file + new_header.save(outfile + ".hdr"); //save the output header file + + if (header.interleave == envi_header::BSQ) { //if the infile is bip file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->select(outfile, bandlist, MASK, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->select(outfile, bandlist, MASK, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + else if (header.interleave == envi_header::BIL) { //if the infile is bip file + if (header.data_type == envi_header::float32) + return ((bil*)file)->select(outfile, bandlist, MASK, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->select(outfile, bandlist, MASK, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + else if (header.interleave == envi_header::BIP) { //if the infile is bip file + if (header.data_type == envi_header::float32) + return ((bip*)file)->select(outfile, bandlist, MASK, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->select(outfile, bandlist, MASK, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + return false; + } + + /// Performs piecewise linear baseline correction of a hyperspectral file/ + + /// @param outfile is the file name for the baseline corrected output + /// @param w is a list of band labels to serve as baseline points (zero values) + bool baseline(std::string outfile, std::vector w, unsigned char* mask = NULL, bool PROGRESS = false){ + header.save(outfile + ".hdr"); + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + if(header.data_type ==envi_header::float32) + return ((bsq*)file)->baseline(outfile, w, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->baseline(outfile,w, mask, PROGRESS); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->baseline(outfile, w, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->baseline(outfile, w, mask, PROGRESS); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->baseline(outfile, w, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->baseline(outfile, w, mask, PROGRESS); + else{ + std::cout<<"ERROR: unidentified data type"< bands, unsigned char* mask, int cuda_device = 0, bool PROGRESS = false) { + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + std::cout<<"ERROR: BSQ projection not supported"<*)file)->project(outfile, center, basis, M, mask, cuda_device, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bip*)file)->project(outfile, center, basis, M, mask, cuda_device, PROGRESS); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->inverse(outfile, center, basis, B, C, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bip*)file)->inverse(outfile, center, basis, B, C, PROGRESS); + else{ + std::cout<<"ERROR: unidentified data type"< BIL + ((bsq*)file)->bil(outfile, PROGRESS, VERBOSE, OPTIMIZATION); + else if(interleave == envi_header::BIP){ //ERROR + //std::cout<<"ERROR: conversion from BSQ to BIP isn't practical; use BSQ->BIL->BIP instead"<*)file)->bip(outfile, PROGRESS, VERBOSE, OPTIMIZATION); + //exit(1); + } + } + + else if(header.data_type == envi_header::float64){ //if the data type is float + if(interleave == envi_header::BSQ){ //ERROR + std::cout<<"ERROR: is already BSQ file"< BIL + ((bsq*)file)->bil(outfile, PROGRESS, OPTIMIZATION); + else if(interleave == envi_header::BIP){ //ERROR + //std::cout<<"ERROR: conversion from BSQ to BIP isn't practical; use BSQ->BIL->BIP instead"<*)file)->bip(outfile, PROGRESS, OPTIMIZATION); + //exit(1); + } + } + + else{ + std::cout<<"ERROR: unidentified data type"< BSQ + ((bil*)file)->bsq(outfile, PROGRESS); + else if(interleave == envi_header::BIP) //BIL -> BIP + ((bil*)file)->bip(outfile, PROGRESS); + } + + else if(header.data_type == envi_header::float64){ + if(interleave == envi_header::BIL){ //ERROR + std::cout<<"ERROR: is already BIL file"< BSQ + ((bil*)file)->bsq(outfile, PROGRESS); + else if(interleave == envi_header::BIP) //BIL -> BIP + ((bil*)file)->bip(outfile, PROGRESS); + } + + else{ + std::cout<<"ERROR: unidentified data type"< BIL + ((bip*)file)->bil(outfile, PROGRESS); + else if(interleave == envi_header::BSQ){ //ERROR + std::cout<<"ERROR: conversion from BIP to BSQ isn't practical; use BIP->BIL->BSQ instead"<*)file)->bip(outfile, PROGRESS); + exit(1); + } + } + + else if(header.data_type == envi_header::float64){ + if(interleave == envi_header::BIP){ //ERROR + std::cout<<"ERROR: is already BIP file"< BIL + ((bip*)file)->bil(outfile, PROGRESS); + else if(interleave == envi_header::BSQ){ //ERROR + std::cout<<"ERROR: conversion from BIP to BSQ isn't practical; use BIP->BIL->BSQ instead"<*)file)->bip(outfile, PROGRESS); + exit(1); + } + } + + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->build_mask(out_mask, mask_band, lower, upper, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->mask_finite(out_mask, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->mask_finite(out_mask, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->mask_finite(out_mask, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bil*)file)->mask_finite(out_mask, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->mask_finite(out_mask, mask, PROGRESS); + else if(header.data_type == envi_header::float64) + ((bip*)file)->mask_finite(out_mask, mask, PROGRESS); + else + std::cout<<"ERROR: unidentified data type"<*)file)->apply_mask(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->apply_mask(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIL){ //if the infile is bil file + if (header.data_type == envi_header::float32) + ((bil*)file)->apply_mask(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->apply_mask(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIP){ //if the infile is bip file + if (header.data_type == envi_header::float32) + ((bip*)file)->apply_mask(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->apply_mask(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else{ + std::cout << "ERROR: unidentified file type" << std::endl; + exit(1); + } + header.save(outfile + ".hdr"); + } + + /// Copies all spectra corresponding to nonzero values of a mask into a pre-allocated matrix of size (P x B) + /// where P is the number of masked pixels and B is the number of bands. The allocated memory can be accessed + /// using the following indexing: i = b*P + p + /// @param matrix is the destination for the pixel data + /// @param p is the mask + bool sift(void* matrix, unsigned char* p = NULL, bool PROGRESS = false){ + + if (header.interleave == envi_header::BSQ){ //if the infile is bsq file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->sift((float*)matrix, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->sift((double*)matrix, p, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->sift((float*)matrix, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->sift((double*)matrix, p, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->sift((float*)matrix, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->sift((double*)matrix, p, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + + } + + /// Saves in an array only those spectra corresponding to nonzero values of the mask. + /// @param outfile is the name of the sifted output file + /// @param p is the mask + bool sift(std::string outfile, unsigned char* p, bool PROGRESS = false) + { + + //calculate the number of non-zero values in the mask + unsigned long long nnz = 0; + unsigned long long npixels = header.lines * header.samples; + for(unsigned long long i = 0; i < npixels; i++) + if( p[i] > 0 ) nnz++; + + //create a new header + envi_header new_header = header; + + //if a BIL file is sifted, it's saved as a BIP + if(header.interleave == envi_header::BIL) + new_header.interleave = envi_header::BIP; + + //set the number of lines to 1 (this is a matrix with 1 line and N samples) + new_header.lines = 1; + new_header.samples = nnz; + new_header.save(outfile + ".hdr"); + + if (header.interleave == envi_header::BSQ){ //if the infile is bsq file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->sift(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->sift(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIL){ //if the infile is bil file + if (header.data_type == envi_header::float32) + return ((bil*)file)->sift(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->sift(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIP){ //if the infile is bip file + if (header.data_type == envi_header::float32) + return ((bip*)file)->sift(outfile, p, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->sift(outfile, p, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else{ + std::cout << "ERROR: unidentified file type" << std::endl; + exit(1); + } + return false; + } + + bool unsift(std::string outfile, unsigned char* mask, unsigned long long samples, unsigned long long lines, bool PROGRESS = false){ + + //create a new header + envi_header new_header = header; + + //set the number of lines and samples in the output file (that's all that changes) + new_header.lines = lines; + new_header.samples = samples; + new_header.save(outfile + ".hdr"); + + + if (header.interleave == envi_header::BSQ){ //if the infile is bsq file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->unsift(outfile, mask, samples, lines, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->unsift(outfile, mask, samples, lines, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else if (header.interleave == envi_header::BIL){ //if the infile is bil file + + std::cout << "ERROR in stim::envi::unsift - BIL files aren't supported yet" << std::endl; + } + + else if (header.interleave == envi_header::BIP){ //if the infile is bip file + + if (header.data_type == envi_header::float32) + return ((bip*)file)->unsift(outfile, mask, samples, lines, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->unsift(outfile, mask, samples, lines, PROGRESS); + else + std::cout << "ERROR: unidentified data type" << std::endl; + } + + else{ + std::cout << "ERROR: unidentified file type" << std::endl; + } + return 0; + } + + /// Compute the ratio of two baseline-corrected peaks. The result is stored in a pre-allocated array. + + /// @param lb1 is the label value for the left baseline point for the first peak (numerator) + /// @param rb1 is the label value for the right baseline point for the first peak (numerator) + /// @param pos1 is the label value for the first peak (numerator) position + /// @param lb2 is the label value for the left baseline point for the second peak (denominator) + /// @param rb2 is the label value for the right baseline point for the second peak (denominator) + /// @param pos2 is the label value for the second peak (denominator) position + /// @param result is a pointer to a pre-allocated array at least X * Y * sizeof(T) in size + bool ph_to_ph(void * result, double lb1, double rb1, double pos1, double lb2, double rb2, double pos2, unsigned char* mask){ + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + if(header.data_type ==envi_header::float32) + return ((bsq*)file)->ph_to_ph((float*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->ph_to_ph((double*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else + std::cout<<"envi::ph_to_ph ERROR - unidentified data type"<*)file)->ph_to_ph((float*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->ph_to_ph((double*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else + std::cout<<"envi::ph_to_ph ERROR - unidentified data type"<*)file)->ph_to_ph((float*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->ph_to_ph((double*)result, lb1, rb1, pos1, lb2, rb2, pos2, mask); + else + std::cout<<"envi::ph_to_ph ERROR - unidentified data type"<*)file)->pa_to_ph((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->pa_to_ph((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->pa_to_ph((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->pa_to_ph((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->pa_to_ph((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->pa_to_ph((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, pos, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->pa_to_pa((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->pa_to_pa((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->pa_to_pa((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->pa_to_pa((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->pa_to_pa((float*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->pa_to_pa((double*)result, lb1, rb1, lab1, rab1, lb2, rb2, lab2, rab2, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->centroid((float*)result, lb1, rb1, lab1, rab1, mask); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->centroid((double*)result, lb1, rb1, lab1, rab1, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->centroid((float*)result, lb1, rb1, lab1, rab1, mask); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->centroid((double*)result, lb1, rb1, lab1, rab1, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->centroid((float*)result, lb1, rb1, lab1, rab1, mask); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->centroid((double*)result, lb1, rb1, lab1, rab1, mask); + else + std::cout<<"ERROR: unidentified data type"<*)file)->close(); + else if(header.data_type == envi_header::float64) + ((bsq*)file)->close(); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->close(); + else if(header.data_type == envi_header::float64) + ((bil*)file)->close(); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->close(); + else if(header.data_type == envi_header::float64) + ((bip*)file)->close(); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->pixel((float*)p, n); + else if(header.data_type == envi_header::float64) + return ((bsq*)file)->pixel((double*)p, n); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->pixel((float*)p, n); + else if(header.data_type == envi_header::float64) + return ((bil*)file)->pixel((double*)p, n); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->pixel((float*)p, n); + else if(header.data_type == envi_header::float64) + return ((bip*)file)->pixel((double*)p, n); + else{ + std::cout<<"ERROR: unidentified data type"<*)file)->band((float*)ptr, wavelength, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->band((double*)ptr, wavelength, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->band((float*)ptr, wavelength, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->band((double*)ptr, wavelength, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->band((float*)ptr, wavelength, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->band((double*)ptr, wavelength, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + void band_bounds(double wavelength, size_t& low, size_t& high) { + if (header.interleave == envi_header::BSQ) { //if the infile is bsq file + if (header.data_type == envi_header::float32) + ((bsq*)file)->band_bounds(wavelength, low, high); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->band_bounds(wavelength, low, high); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL) { + if (header.data_type == envi_header::float32) + ((bil*)file)->band_bounds(wavelength, low, high); + else if (header.data_type == envi_header::float64) + ((bil*)file)->band_bounds(wavelength, low, high); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP) { + if (header.data_type == envi_header::float32) + ((bip*)file)->band_bounds(wavelength, low, high); + else if (header.data_type == envi_header::float64) + ((bip*)file)->band_bounds(wavelength, low, high); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + } + + // Retrieve a spectrum at the specified 1D location + + /// @param ptr is a pointer to pre-allocated memory of size B*sizeof(T) + /// @param x is the 1D coordinate of the spectrum + template + void spectrum(T* ptr, size_t n, bool PROGRESS = false){ + + void* temp = alloc_array(header.bands); //allocate space for the output array + + if(header.interleave == envi_header::BSQ){ //if the infile is bsq file + if(header.data_type ==envi_header::float32){ + ((bsq*)file)->spectrum((float*)temp, n, PROGRESS); + cast(ptr, (float*)temp, header.bands); + } + else if (header.data_type == envi_header::float64){ + ((bsq*)file)->spectrum((double*)temp, n, PROGRESS); + cast(ptr, (double*)temp, header.bands); + } + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32){ + ((bil*)file)->spectrum((float*)temp, n, PROGRESS); + cast(ptr, (float*)temp, header.bands); + } + else if (header.data_type == envi_header::float64){ + ((bil*)file)->spectrum((double*)temp, n, PROGRESS); + cast(ptr, (double*)temp, header.bands); + } + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32){ + ((bip*)file)->spectrum((float*)temp, n, PROGRESS); + float test = ((float*)temp)[0]; + cast(ptr, (float*)temp, header.bands); + } + else if (header.data_type == envi_header::float64){ + ((bip*)file)->spectrum((double*)temp, n, PROGRESS); + cast(ptr, (double*)temp, header.bands); + } + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + free(temp); + } + + /// Retrieve a spectrum from the specified (x, y) location + + /// @param ptr is a pointer to pre-allocated memory of size B*sizeof(T) + /// @param x is the x-coordinate of the spectrum + /// @param y is the y-coordinate of the spectrum + template + void spectrum(T* ptr, size_t x, size_t y, bool PROGRESS = false){ + + spectrum(ptr, y * header.samples + x, PROGRESS); + } + + /// Retrieve a single band (based on index) and stores it in pre-allocated memory. + + /// @param p is a pointer to an allocated region of memory at least X * Y * sizeof(T) in size. + /// @param page <= B is the integer number of the band to be copied. + bool band_index(void* ptr, unsigned long long b){ + if (header.interleave == envi_header::BSQ){ //if the infile is bsq file + if (header.data_type == envi_header::float32) + return ((bsq*)file)->band_index((float*)ptr, b); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->band_index((double*)ptr, b); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->band_index((float*)ptr, b); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->band_index((double*)ptr, b); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->band_index((float*)ptr, b); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->band_index((double*)ptr, b); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + /// Calculate the mean value for all masked (or valid) pixels in a band and returns the average spectrum + + /// @param p is a pointer to pre-allocated memory of size [B * sizeof(T)] that stores the mean spectrum + /// @param mask is a pointer to memory of size [X * Y] that stores the mask value at each pixel location + bool mean_spectrum(double * p, double* std, unsigned char* mask, bool PROGRESS = false){ + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + return ((bsq*)file)->mean_spectrum(p, std, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->mean_spectrum(p, std, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->mean_spectrum(p, std, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->mean_spectrum(p, std, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->mean_spectrum(p, std, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->mean_spectrum(p, std, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + /// Calculate the mean value for all masked (or valid) pixels in a band and returns the average spectrum + + /// @param p is a pointer to pre-allocated memory of size [B * sizeof(T)] that stores the mean spectrum + /// @param mask is a pointer to memory of size [X * Y] that stores the mask value at each pixel location + bool median_spectrum(double* m, unsigned char* mask, bool PROGRESS = false){ + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + return ((bsq*)file)->median_spectrum(m, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->median_spectrum(m, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else{ + std::cout<<"ERROR: median calculation is only supported for BSQ interleave types. Convert to process."<*)file)->co_matrix(co, avg, mask, cuda_device, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->co_matrix(co, avg, mask, cuda_device, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->co_matrix(co, avg, mask, cuda_device, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->co_matrix(co, avg, mask, cuda_device, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + /// Calculate the covariance of noise matrix for all masked pixels in the image. + + /// @param co is a pointer to pre-allocated memory of size [B * B] that stores the resulting covariance matrix + /// @param avg is a pointer to memory of size B that stores the average spectrum + /// @param mask is a pointer to memory of size [X * Y] that stores the mask value at each pixel location + bool coNoise_matrix(double* coN, double* avg, unsigned char* mask, int cuda_device = 0, bool PROGRESS = false){ + if (header.interleave == envi_header::BSQ){ + std::cout<<"ERROR: calculating the covariance matrix of noise for a BSQ file is impractical; convert to BIP first"<*)file)->coNoise_matrix(coN, avg, mask, cuda_device, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->coNoise_matrix(coN, avg, mask, cuda_device, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + /// Crop a region of the image and save it to a new file. + + /// @param outfile is the file name for the new cropped image + /// @param x0 is the lower-left x pixel coordinate to be included in the cropped image + /// @param y0 is the lower-left y pixel coordinate to be included in the cropped image + /// @param x1 is the upper-right x pixel coordinate to be included in the cropped image + /// @param y1 is the upper-right y pixel coordinate to be included in the cropped image + bool crop(std::string outfile, + unsigned long long x0, + unsigned long long y0, + unsigned long long x1, + unsigned long long y1, + unsigned long long b0, + unsigned long long b1, + bool PROGRESS = false){ + + //save the header for the cropped file + stim::envi_header new_header = header; + new_header.samples = x1 - x0 + 1; + new_header.lines = y1 - y0 + 1; + new_header.bands = b1 - b0 + 1; + std::vector::const_iterator first = new_header.wavelength.begin() + b0; + std::vector::const_iterator last = new_header.wavelength.begin() + b1 + 1; + new_header.wavelength = std::vector(first, last); + new_header.save(outfile + ".hdr"); + + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + return ((bsq*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->crop(outfile, x0, y0, x1, y1, b0, b1, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + return false; + } + + void subimages(std::string outfile, size_t nx, size_t ny, unsigned char* mask, bool PROGRESS = false){ + + size_t nnz = 0; //initialize the number of subimages to zero + for(size_t i = 0; i < header.lines * header.samples; i++) //for each pixel in the mask + if(mask[i]) nnz++; //if the pixel is valid, add a subimage + + + //save the header for the cropped file + stim::envi_header new_header = header; + new_header.samples = nx; //calculate the width of the output image (concatenated subimages) + new_header.lines = nnz * ny; //calculate the height of the output image (height of subimages) + + + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->subimages(outfile, nx, ny, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->subimages(outfile, nx, ny, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + else if (header.interleave == envi_header::BIP){ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + + new_header.save(outfile + ".hdr"); //save the header for the output file + } + + /// Remove a list of bands from the ENVI file + + /// @param outfile is the file name for the output hyperspectral image (with trimmed bands) + /// @param b is an array of bands to be eliminated + void trim(std::string outfile, std::vector trimmed, bool PROGRESS = false){ + + envi_header h = header; + h.bands = header.bands - trimmed.size(); //calculate the new number of bands + if(header.wavelength.size() != 0) + h.wavelength.resize(h.bands); + if(header.band_names.size() != 0) + h.band_names.resize(h.bands); + size_t it = 0; //allocate an index into the trimmed bands array + size_t i = 0; + for(size_t b = 0; b < header.bands; b++){ //for each band + if(b != trimmed[it]){ + if(h.wavelength.size()) h.wavelength[i] = header.wavelength[b]; + if(h.band_names.size()) h.band_names[i] = header.band_names[i]; + i++; + } + else it++; + } + h.save(outfile + ".hdr"); + + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + return ((bsq*)file)->trim(outfile, trimmed, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bsq*)file)->trim(outfile, trimmed, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + return ((bil*)file)->trim(outfile, trimmed, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bil*)file)->trim(outfile, trimmed, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + return ((bip*)file)->trim(outfile, trimmed, PROGRESS); + else if (header.data_type == envi_header::float64) + return ((bip*)file)->trim(outfile, trimmed, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + + } + + /// Combine two ENVI images along the Y axis + + /// @param outfile is the combined file to be output + /// @param C is the ENVI object for the image to be combined + void combine(std::string outfile, envi C, long long x, long long y, bool PROGRESS = false){ + envi_header h = header; + + long long left = std::min(0, x); //calculate the left edge of the final image + long long right = std::max((long long)header.samples, C.header.samples + x); //calculate the right edge of the final image + long long top = std::min(0, y); //calculate the top edge of the final image + long long bottom = std::max((long long)header.lines, C.header.lines + y); //calculate the bottom edge of the final image + + h.samples = right - left; + h.lines = bottom - top; + + h.save(outfile + ".hdr"); + + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->combine(outfile, (bsq*)C.file, x, y, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->combine(outfile, (bsq*)C.file, x, y, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + ((bil*)file)->combine(outfile, (bil*)C.file, x, y, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->combine(outfile, (bil*)C.file, x, y, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->combine(outfile, (bip*)C.file, x, y, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->combine(outfile, (bip*)C.file, x, y, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + } + + void append(std::string outfile, envi C, bool PROGRESS = false) { + if (C.header.samples != header.samples || //verify that the images are the same size + C.header.lines != header.lines) { + std::cout << "ERROR - appended images must be the same size: input = [" << header.samples << " x " << header.lines << "], output = [" << C.header.samples << " x " << C.header.lines << "]" << std::endl; + exit(1); + } + if (C.header.interleave != header.interleave) { + std::cout << "ERROR - appended images must have the same interleave format" << std::endl; + exit(1); + } + + stim::envi_header new_header = header; //create a header for the output image + new_header.bands = header.bands + C.header.bands; //calculate the number of bands in the new image + + if (header.wavelength.size() != 0 && C.header.wavelength.size() != 0) { //if both files contain wavelength information + for (size_t b = 0; b < C.header.wavelength.size(); b++) + new_header.wavelength.push_back(C.header.wavelength[b]); //append the wavelength labels to the new header array + } + else new_header.wavelength.clear(); + + if (header.band_names.size() != 0 && C.header.band_names.size() != 0) { //if both files contain band name information + for (size_t b = 0; b < C.header.band_names.size(); b++) + new_header.band_names.push_back(C.header.band_names[b]); //append the wavelength labels to the new header array + } + else new_header.wavelength.clear(); + + new_header.save(outfile + ".hdr"); //save the output + + if (header.interleave == envi_header::BSQ) { + if (header.data_type == envi_header::float32) + ((bsq*)file)->append(outfile, (bsq*)C.file, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->append(outfile, (bsq*)C.file, PROGRESS); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL) { + if (header.data_type == envi_header::float32) + ((bil*)file)->append(outfile, (bil*)C.file, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->append(outfile, (bil*)C.file, PROGRESS); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP) { + if (header.data_type == envi_header::float32) + ((bip*)file)->append(outfile, (bip*)C.file, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->append(outfile, (bip*)C.file, PROGRESS); + else { + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + } + + /// Convolve the given band range with a kernel specified by a vector of coefficients. + + /// @param outfile is the combined file to be output + /// @param c is an array of coefficients + /// @param start is the band to start processing (the first coefficient starts here) + /// @param nbands is the number of bands to process + /// @param center is the index for the center coefficient for the kernel (used to set the wavelengths in the output file) + void convolve(std::string outfile, std::vector C, size_t start, size_t end, size_t center = 0, unsigned char* mask = NULL, bool PROGRESS = false){ + size_t nbands = end - start + 1; + envi_header h = header; //copy the current header + h.bands = nbands; //set the number of new bands + if(header.wavelength.size() != 0){ + h.wavelength.resize(nbands); //set the number of wavelengths to the number of bands + for(size_t b = 0; b < nbands; b++) + h.wavelength[b] = header.wavelength[b+center]; + } + if(header.band_names.size() != 0){ + h.band_names.resize(nbands); + for(size_t b = 0; b < nbands; b++) + h.band_names[b] = header.band_names[b+center]; + } + h.save(outfile + ".hdr"); //save the new header + + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + ((bil*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->convolve(outfile, C, start, end, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + } + + /// Approximates the nth derivative of the spectra to the specified order + + /// @param outfile is the file where the derivative approximation will be saved + /// @n is the derivative to be calculated + /// @order is the order of the error (must be even) + void deriv(std::string outfile, size_t d, size_t order, unsigned char* mask = NULL, bool PROGRESS = false){ + header.save(outfile + ".hdr"); + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + ((bil*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->deriv(outfile, d, order, header.wavelength, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + exit(1); + } + + void multiply(std::string outfile, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + header.save(outfile + ".hdr"); + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->multiply(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->multiply(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + ((bil*)file)->multiply(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->multiply(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->multiply(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->multiply(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + exit(1); + } + + void add(std::string outfile, double v, unsigned char* mask = NULL, bool PROGRESS = false){ + header.save(outfile + ".hdr"); + if (header.interleave == envi_header::BSQ){ + if (header.data_type == envi_header::float32) + ((bsq*)file)->add(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bsq*)file)->add(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIL){ + if (header.data_type == envi_header::float32) + ((bil*)file)->add(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bil*)file)->add(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + + else if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->add(outfile, v, mask, PROGRESS); + else if (header.data_type == envi_header::float64) + ((bip*)file)->add(outfile, v, mask, PROGRESS); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + exit(1); + } + + + + + void fft(std::string outfile, double band_min, double band_max, size_t samples = 0, void* ratio = NULL, size_t rx = 0, size_t ry = 0, bool PROGRESS = false, int cuda_device = 0){ + if(samples == 0) samples = header.bands; + //double B = (double)header.bands; + double delta = header.wavelength[1] - header.wavelength[0]; //calculate spacing in the current domain + double span = samples * delta; //calculate the span in the current domain + double fft_delta = 1.0 / span; //calculate the span in the FFT domain + double fft_max = fft_delta * samples/2; //calculate the maximum range of the FFT + + if(band_max > fft_max) band_max = fft_max; //the user gave a band outside of the FFT range, reset the band to the maximum available + size_t start_i = (size_t)std::ceil(band_min / fft_delta); //calculate the first band to store + size_t size_i = (size_t)std::floor(band_max / fft_delta) - start_i + 1; //calculate the number of bands to store + size_t end_i = start_i + size_i - 1; //last band number + + envi_header new_header = header; + new_header.bands = size_i; + new_header.set_wavelengths(start_i * fft_delta, fft_delta); + new_header.wavelength_units = "inv_" + header.wavelength_units; + new_header.save(outfile + ".hdr"); + + if (header.interleave == envi_header::BIP){ + if (header.data_type == envi_header::float32) + ((bip*)file)->fft(outfile, start_i, end_i, samples, (float*)ratio, rx, ry, PROGRESS, cuda_device); + else if (header.data_type == envi_header::float64) + ((bip*)file)->fft(outfile, start_i, end_i, samples, (double*)ratio, rx, ry, PROGRESS, cuda_device); + else{ + std::cout << "ERROR: unidentified data type" << std::endl; + exit(1); + } + } + else{ + std::cout<<"ERROR: only BIP files supported for FFT"< +#include +#include +#include +#include +#include +#include +#include + +//information from an ENVI header file +//A good resource can be found here: http://www.exelisvis.com/docs/enviheaderfiles.html + +namespace stim{ + +struct envi_header +{ + enum dataType {dummy0, int8, int16, int32, float32, float64, complex32, dummy7, dummy8, complex64, dummy10, dummy11, uint16, uint32, int64, uint64}; + enum interleaveType {BIP, BIL, BSQ}; //bip = Z,X,Y; bil = X,Z,Y; bsq = X,Y,Z + enum endianType {endianLittle, endianBig}; + + std::string name; + + std::string description; + + size_t samples; //x-axis + size_t lines; //y-axis + size_t bands; //spectral axis + size_t header_offset; //header offset for binary file (in bytes) + std::string file_type; //should be "ENVI Standard" + + envi_header::dataType data_type; //data representation; common value is 4 (32-bit float) + + + envi_header::interleaveType interleave; + + std::string sensor_type; //not really used + + envi_header::endianType byte_order; //little = least significant bit first (most systems) + + double x_start, y_start; //coordinates of the upper-left corner of the image + std::string wavelength_units; //stored wavelength units + std::string z_plot_titles[2]; + + double pixel_size[2]; //pixel size along X and Y + + std::vector band_names; //name for each band in the image + std::vector wavelength; //wavelength for each band + + void init(){ + name = ""; + + //specify default values for a new or empty ENVI file + samples = 0; + lines = 0; + bands = 0; + header_offset = 0; + data_type = float32; + interleave = BSQ; + byte_order = endianLittle; + x_start = y_start = 0; + pixel_size[0] = pixel_size[1] = 1; + + //strings + file_type = "ENVI Standard"; + sensor_type = "Unknown"; + wavelength_units = "Unknown"; + z_plot_titles[0] = z_plot_titles[1] = "Unknown"; + } + + envi_header(){ + init(); + } + envi_header(std::string name){ + init(); + load(name); + } + + //sets the wavelength vector given a starting value and uniform step size + void set_wavelengths(double start, double step){ + size_t B = bands; //get the number of bands + wavelength.resize(B); + for(size_t b = 0; b < B; b++) + wavelength[b] = start + b * step; + } + + std::string trim(std::string line){ + + if(line.length() == 0) + return line; + //trims whitespace from the beginning and end of line + size_t start_i, end_i; + for(start_i=0; start_i < line.length(); start_i++) + if(line[start_i] != 32) + { + break; + } + + for(end_i = line.length()-1; end_i >= start_i; end_i--) + if(line[end_i] != ' ' && line[end_i] != '\r') + { + break; + } + + return line.substr(start_i, end_i - start_i+1); + } + + + std::string get_token(std::string line){ + //returns a variable name; in this case we look for the '=' sign + size_t i = line.find_first_of('='); + + std::string result; + if(i != std::string::npos) + result = trim(line.substr(0, i-1)); + + return result; + } + + std::string get_data_str(std::string line){ + size_t i = line.find_first_of('='); + + std::string result; + if(i != std::string::npos) + result = trim(line.substr(i+1)); + else + { + std::cout<<"ENVI Header error - data not found for token: "< get_string_seq(std::string token, std::string sequence) + { + //this function returns a sequence of comma-delimited strings + std::vector result; + + std::string entry; + size_t i; + do + { + i = sequence.find_first_of(','); + entry = sequence.substr(0, i); + sequence = sequence.substr(i+1); + result.push_back(trim(entry)); + }while(i != std::string::npos); + + return result; + } + + std::vector get_double_seq(std::string token, std::string sequence) + { + //this function returns a sequence of comma-delimited strings + std::vector result; + std::string entry; + size_t i; + do + { + i = sequence.find_first_of(','); + entry = sequence.substr(0, i); + sequence = sequence.substr(i+1); + result.push_back(atof(entry.c_str())); + //std::cout<>line; + if(line != "ENVI") + { + std::cout<<"ENVI Header Error: The header doesn't appear to be an ENVI file. The first line should be 'ENVI'."< pxsize = get_double_seq(token, string_sequence); + pixel_size[0] = pxsize[0]; + pixel_size[1] = pxsize[1]; + } + else if(token == "z plot titles") + { + std::string string_sequence = get_brace_str(token, line, file); + std::vector titles = get_string_seq(token, string_sequence); + z_plot_titles[0] = titles[0]; + z_plot_titles[1] = titles[1]; + } + + else if(token == "samples") + samples = atoi(get_data_str(line).c_str()); + else if(token == "lines") + lines = atoi(get_data_str(line).c_str()); + else if(token == "bands") + bands = atoi(get_data_str(line).c_str()); + else if(token == "header offset") + header_offset = atoi(get_data_str(line).c_str()); + else if(token == "file type") + file_type = get_data_str(line); + else if(token == "data type") + data_type = (dataType)atoi(get_data_str(line).c_str()); + else if(token == "interleave") + { + std::string interleave_str = get_data_str(line); + if(interleave_str == "bip") + interleave = BIP; + else if(interleave_str == "bil") + interleave = BIL; + else if(interleave_str == "bsq") + interleave = BSQ; + } + else if(token == "sensor type") + sensor_type = get_data_str(line); + else if(token == "byte order") + byte_order = (endianType)atoi(get_data_str(line).c_str()); + else if(token == "x start") + x_start = atof(get_data_str(line).c_str()); + else if(token == "y start") + y_start = atof(get_data_str(line).c_str()); + else if(token == "wavelength units") + wavelength_units = get_data_str(line); + + //get the next line + getline(file, line); + } + + //make sure the number of bands matches the number of wavelengths + size_t wavelengths = wavelength.size(); + if(wavelengths && bands != wavelengths) + { + std::cout<<"ENVI Header Error -- Number of wavelengths doesn't match the number of bands. Bands = "< 0) + { + outfile<<"band names = {"< band_index(double w){ + std::vector idx; //create an empty array of indices + if(wavelength.size() == 0){ //if a wavelength vector doesn't exist, assume the passed value is a band + if(w < 0 || w > bands-1) return idx; //if the band is outside the given band range, return an empty vector + size_t low, high; //allocate space for the floor and ceiling + low = (size_t)std::floor(w); //calculate the floor + high = (size_t)std::ceil(w); //calculate the ceiling + if(low == high) //if the floor and ceiling are the same + idx.push_back(low); //return a vector with one element (the given w matches a band exactly) + else{ + idx.resize(2); //otherwise return the floor and ceiling + idx[0] = low; + idx[1] = high; + } + return idx; + } + else if(w < wavelength[0] || w > wavelength[bands-1]) return idx; //if the wavelength range is outside of the file, return an empty array + + for(size_t b = 0; b < bands; b++){ //for each band in the wavelength vector + if(wavelength[b] == w){ //if an exact match is found + idx.push_back(b); //add the band to the array and return it + return idx; + } + if(wavelength[b] >= w){ //if the current wavelength exceeds w + idx.resize(2); + idx[0] = b-1; //push both the previous and current band index + idx[1] = b; + return idx; //return the pair of indices + } + } + return idx; + } + + /// Convert a wavelength range to a list of bands + std::vector band_indices(double w0, double w1){ + + size_t idx0 = 0; + size_t idx1 = 0; //declare the interval indices for the band range + + //get the indices for the first wavelength + std::vector r; + + if(w0 > wavelength[bands-1] || w1 < wavelength[0]){ + std::cout<<"ERROR in envi_header::band_indices - wavelengths are completely out of range: ["< w0) + } + //get the indices for the second wavelength + if(w1 > wavelength[bands-1]) + idx1 = bands-1; + else{ + r = band_index(w1); + if(r.size() == 0) + idx1 = bands - 1; + else + idx1 = r[0]; //take the lowest band index (this is the last band < w1) + } + + size_t n = idx1 - idx0 + 1; //calculate the number of bands in this interval + r.resize(n); //resize the band index array + for(size_t b = 0; b < n; b++){ //for each band in the interval + r[b] = idx0 + b; // insert the band in the array + } + + return r; //return the index array + } +}; //end EnviHeader +} +#endif diff --git a/tira/envi/hsi.h b/tira/envi/hsi.h new file mode 100644 index 0000000..43ad653 --- /dev/null +++ b/tira/envi/hsi.h @@ -0,0 +1,228 @@ +#ifndef STIM_HSI_H +#define STIM_HSI_H + +#include +#include +#include +#include + +#ifdef _WIN32 + #include +#else + #include +#endif + +namespace stim{ + +/** + The BIL class represents a 3-dimensional binary file stored using band interleaved by line (BIL) image encoding. The binary file is stored + such that X-Z "frames" are stored sequentially to form an image stack along the y-axis. When accessing the data sequentially on disk, + the dimensions read, from fastest to slowest, are X, Z, Y. + + This class is optimized for data streaming, and therefore supports extremely large (terabyte-scale) files. Data is loaded from disk + on request. Functions used to access data are written to support efficient reading. +*/ + +template +class hsi: public binary { + +protected: + unsigned char O[3]; //order of dimensions (orientation on disk) + //[X Y B]: [0 1 2] = bsq, [0 2 1] = bil, [1 2 0] = bip + + std::vector w; //band wavelengths + + using binary::R; + using binary::progress; + + unsigned long long X(){ return R[O[0]]; } + unsigned long long Y(){ return R[O[1]]; } + unsigned long long Z(){ return R[O[2]]; } + + /// Initialize axis orders based on common interleave formats + void init_bsq(){ O[0] = 0; O[1] = 1; O[2] = 2; } + void init_bil(){ O[0] = 0; O[1] = 2; O[2] = 1; } + void init_bip(){ O[0] = 1; O[1] = 2; O[2] = 0; } + + /// Calculate the number of masked pixels in a given mask + unsigned long long nnz(unsigned char* mask){ + unsigned long long XY = X() * Y(); //calculate the total number of pixels in the HSI + if(mask == NULL) return XY; //if the mask is null, assume that all of the pixels are masked + + unsigned long long n = 0; //initialize the number of masked pixels to zero (0) + for(unsigned long long xy = 0; xy < XY; xy++) //for each pixel in the HSI + if(mask[xy]) n++; //if the mask value is nonzero, increment the number of masked pixels + return n; //return the number of masked pixels + } + + //perform linear interpolation between two bands + T lerp(double w, T low_v, double low_w, T high_v, double high_w){ + if(low_w == high_w) return low_v; //if the interval is of zero length, just return one of the bounds + double alpha = (w - low_w) / (high_w - low_w); //calculate the interpolation factor + return (T)((1.0 - alpha) * low_v + alpha * high_v); //interpolate + } + + /// Returns the interpolated in the given spectrum based on the given wavelength + + /// @param s is the spectrum in main memory of length Z() + /// @param wavelength is the wavelength value to interpolate out + T interp_spectrum(T* s, double wavelength){ + size_t low, high; //indices for the bands surrounding wavelength + band_bounds(wavelength, low, high); //get the surrounding band indices + + if(high == w.size()) return s[w.size()-1]; //if the high band is above the wavelength range, return the highest wavelength value + + return lerp(wavelength, s[low], w[low], s[high], w[high]); + } + + /// Returns the interpolated value corresponding to the given list of wavelengths + std::vector interp_spectrum(T* s, std::vector wavelengths){ + + unsigned long long N = wavelengths.size(); //get the number of wavelength measurements + + std::vector v; //allocate space for the resulting values + v.resize(wavelengths.size()); + for(unsigned long long n = 0; n < N; n++){ //for each measurement + v[n] = interp_spectrum(s, wavelengths[n]); //interpolate the measurement + } + return v; + } + + /// Returns the 1D on-disk index of a specified pixel location and band + size_t idx(size_t x, size_t y, size_t b){ + size_t c[3]; //generate a coefficient list + + c[O[0]] = x; //assign the coordinates based on the coefficient order + c[O[1]] = y; + c[O[2]] = b; + + return c[2] * R[0] * R[1] + c[1] * R[0] + c[0]; //calculate and return the index (trust me this works) + } + + /// Returns the 3D coordinates of a specified index + void xyb(size_t idx, size_t& x, size_t& y, size_t& b){ + + size_t c[3]; + + c[2] = idx / (R[0] * R[1]); + c[1] = (idx - c[2] * R[0] * R[1]) / R[0]; + c[0] = idx - c[2] * R[0] * R[1] - c[1] * R[0]; + + x = c[O[0]]; + y = c[O[1]]; + b = c[O[2]]; + } + +public: + + /// Gets the two band indices surrounding a given wavelength + void band_bounds(double wavelength, size_t& low, size_t& high) { + size_t B = Z(); + for (high = 0; high < B; high++) { + if (w[high] > wavelength) break; + } + low = 0; + if (high > 0) + low = high - 1; + } + + /// Get the list of band numbers that bound a list of wavelengths + void band_bounds(std::vector wavelengths, + std::vector& low_bands, + std::vector& high_bands) { + + unsigned long long W = w.size(); //get the number of wavelengths in the list + low_bands.resize(W); //pre-allocate space for the band lists + high_bands.resize(W); + + for (size_t wl = 0; wl < W; wl++) { //for each wavelength + band_bounds(wavelengths[wl], low_bands[wl], high_bands[wl]); //find the low and high bands + } + } + /// Get a mask that has all pixels with inf or NaN values masked out (false) + void mask_finite(unsigned char* out_mask, unsigned char* mask, bool PROGRESS = false){ + size_t XY = X() * Y(); + if(mask == NULL) //if no mask is provided + memset(out_mask, 255, XY * sizeof(unsigned char)); //initialize the mask to 255 + else //if a mask is provided + memcpy(out_mask, mask, XY * sizeof(unsigned char)); //initialize the current mask to that one + T* page = (T*)malloc(R[0] * R[1] * sizeof(T)); //allocate space for a page of data + + for(size_t p = 0; p < R[2]; p++){ //for each page + binary::read_page(page, p); //read a page + for(size_t i = 0; i < R[0] * R[1]; i++){ //for each pixel in that page + +#ifdef _WIN32 + if(!_finite(page[i])){ //if the value at index i is not finite +#else + if(!std::isfinite(page[i])){ //C++11 implementation +#endif + size_t x, y, b; + xyb(p * R[0] * R[1] + i, x, y, b); //find the 3D coordinates of the value + out_mask[ y * X() + x ] = 0; //remove the pixel (it's bad) + } + } + if(PROGRESS) progress = (double)(p + 1) / (double)R[2] * 100; + } + } + + void calc_combined_size(long long xp, long long yp, long long Sx, long long Sy, + size_t& Sfx, size_t& Sfy){ + long long left = std::min(0, xp); //calculate the left edge of the final image + long long right = std::max((long long)X(), Sx + xp); //calculate the right edge of the final image + long long top = std::min(0, yp); //calculate the top edge of the final image + long long bottom = std::max((long long)Y(), Sy + yp); //calculate the bottom edge of the final image + + Sfx = right - left; + Sfy = bottom - top; //calculate the size of the final image + } + + /// Calculates the necessary image size required to combine two images given the specified offset (position) of the second image + void calc_combined_size(long long xp, long long yp, long long Sx, long long Sy, + size_t& Sfx, size_t& Sfy, + size_t& p0_x, size_t& p0_y, + size_t& p1_x, size_t& p1_y){ + + calc_combined_size(xp, yp, Sx, Sy, Sfx, Sfy); + + p0_x = p0_y = p1_x = p1_y = 0; //initialize all boundary positions to zero + + if(xp < 0) p0_x = -xp; //set the left positions of the current and source image + else p1_x = xp; + if(yp < 0) p0_y = -yp; + else p1_y = yp; + } + + /// Inserts an image of a band into a larger image + void pad_band(T* padded, T* src, size_t x0, size_t x1, size_t y0, size_t y1){ + + size_t w = X(); //calculate the number of pixels in a line + size_t wb = w * sizeof(T); //calculate the number of bytes in a line + size_t pw = (X() + x0 + x1); //calculate the width of the padded image + size_t pwb = pw * sizeof(T); //calculate the number of bytes in a line of the padded image + + for(size_t y = 0; y < Y(); y++){ //for each line in the real image + memcpy( &padded[ (y + y0) * pw + x0 ], &src[y * w], wb ); //use memcpy to copy the line to the appropriate place in the padded image + } + } + + size_t read(T* dest, size_t x, size_t y, size_t z, size_t sx, size_t sy, size_t sz){ + size_t d[3]; //position in the binary coordinate system + size_t sd[3]; //size in the binary coordinate system + + d[O[0]] = x; //set the point in the binary coordinate system + d[O[1]] = y; + d[O[2]] = z; + + sd[O[0]] = sx; //set the size in the binary coordinate system + sd[O[1]] = sy; + sd[O[2]] = sz; + + return binary::read(dest, d[0], d[1], d[2], sd[0], sd[1], sd[2]); + } + +}; + +} //end namespace STIM + +#endif diff --git a/tira/gl/error.h b/tira/gl/error.h new file mode 100644 index 0000000..13ae29c --- /dev/null +++ b/tira/gl/error.h @@ -0,0 +1,16 @@ +#ifndef RTS_OPENGL_ERROR +#define RTS_OPENGL_ERROR + +#include +#include +#include + +#define CHECK_OPENGL_ERROR \ +{ GLenum error; \ + while ( (error = glGetError()) != GL_NO_ERROR) { \ + printf( "OpenGL ERROR: %s\nCHECK POINT: %s (line %d)\n", gluErrorString(error), __FILE__, __LINE__ ); \ + } \ +} + + +#endif \ No newline at end of file diff --git a/tira/gl/gl_spider.h b/tira/gl/gl_spider.h new file mode 100644 index 0000000..3227115 --- /dev/null +++ b/tira/gl/gl_spider.h @@ -0,0 +1,1867 @@ + #ifndef STIM_GL_SPIDER_H +#define STIM_GL_SPIDER_H + +//#include +///basic GL and Cuda libs +#include +#include +#include +#include + +///stim .*h for gl tracking and visualization +#include +#include +#include +#include +#include +#include +#include + +///stim *.h for CUDA +#include +#include + +///stim *.h for branch detection +#include +#include +#include + +/// *.h for math +#include +#include +#include +#include +#include +#include +#include + + +///c++ libs for everything +#include +#include +#include +#include + +#ifdef TIMING + #include + #include +#endif + +#ifdef TESTING + #include + #include +#endif + +#ifdef DEBUG + #include +#endif + +namespace stim +{ + +template +class gl_spider // : public virtual gl_texture +{ + //doen't use gl_texture really, just needs the GLuint id. + //doesn't even need the texture iD really. + private: + + #ifdef TIMING + double branch_time;// = 0; + double direction_time;// = 0; + double position_time;// = 0; + double size_time;// = 0; + double cost_time;// = 0; + double network_time;// = 0; + double hit_time;// = 0; + #endif + + stim::vec3 p; //vector designating the position of the spider. + stim::vec3 d; //normalized direction of travel + float m; //size of the spider in tissue space. + + std::vector > dV; //A list of all the direction vectors. + std::vector > pV; //A list of all test positions (relative to p) + std::vector mV; //A list of all the size vectors. + std::vector lV; //A list of all the size vectors. + + stim::matrix_sq cT; //current Transformation matrix (tissue)->(texture) + GLuint texID; //OpenGL ID for the texture to be traced + stim::vec3 S; //Size of a voxel in the volume. + stim::vec3 R; //Dimensions of the volume. + + + //GL and Cuda variables + GLuint dList; //ID of the starting display lists (series of 4) + //dList + 0 = direction template rectangles + //dList + 1 = position template rectangles + //dList + 2 = size template rectangles + //dList + 3 = branch detection cylinder around the fiber + + GLuint fboID; //framebuffer ID for direction templates + GLuint texbufferID; //texture ID for direction templates + GLuint direction_buffID; //framebuffer ID, position templates + GLuint direction_texID; //texture ID, position templates + + GLuint position_buffID; //framebuffer ID, position templates + GLuint position_texID; //texture ID, position templates + + GLuint radius_buffID; //framebuffer ID, radius templates + GLuint radius_texID; //texture ID, radius templates + + GLuint length_buffID; //framebuffer ID, radius templates + GLuint length_texID; //texture ID, radius templates + + GLuint cylinder_buffID; //framebuffer ID, cylinder (surrounding fiber) + GLuint cylinder_texID; //texture ID, cylinder + + int numSamples; //The number of templates in the buffer. + int numSamplesPos; + int numSamplesMag; + + float length; //this will be a function of the radius + float stepsize; //this will be a function of the length + + int current_cost; //variable to store the cost of the current step + + //Tracing variables. + std::stack< stim::vec3 > seeds; //seed positions + std::stack< stim::vec3 > seedsvecs; //seed directions + std::stack< float > seedsmags; //seed magnitudes + + std::vector< stim::vec3 > cL; //centerline up to the current point + std::vector< stim::vec3 > cD; //directions up to the current point (debugging) + std::vector< float > cM; //radius up to the current point + std::vector< float > cLen; //radius up to the current point + + stim::gl_network nt; //network object holding the currently traced centerlines + stim::glObj sk; //OBJ file storing the network (identical to above) + + //consider replacing with two seed points facing opposite directions + stim::vec rev; //reverse vector + + //selection mode - detecting fiber intersections + stim::camera camSel; //camera for selection mode (detecting collisions) + stim::vec3 ps; //position for the selection camera + stim::vec3 ups; //up direction for the selection camera + stim::vec3 ds; //direction for the selection camera + + float n_pixels; //length of the template (in pixels) + + //cuda texture variables that keep track of the binding. + stim::cuda::cuda_texture t_dir; //cuda_texture object used as an interface between OpenGL and cuda for direction vectors. + stim::cuda::cuda_texture t_pos; //cuda_texture object used as an interface between OpenGL and cuda for position vectors. + stim::cuda::cuda_texture t_mag; //cuda_texture object used as an interface between OpenGL and cuda for size vectors. + stim::cuda::cuda_texture t_len; //cuda_texture object used as an interface between OpenGL and cuda for size vectors. + + int last_fiber; //variable that tracks the last fiber hit during tracing. -1 if no fiber was hit. + + + #ifdef DEBUG + int iter; + stringstream name; + int iter_pos; + int iter_dir; + int iter_siz; + #endif + +//--------------------------------------------------------------------------// +//-------------------------------PRIVATE METHODS----------------------------// +//--------------------------------------------------------------------------// + + /// Method for finding the best scale for the spider. + /// changes the x, y, z size of the spider to minimize the cost + /// function. + void + findOptimalDirection() + { + #ifdef TIMING + gpuStartTimer(); //Timer for profiling + #endif + setMatrix(); //create the transformation matrix. + glCallList(dList); //move the templates to p, d, m. + glFinish(); //flush the pipeline + #ifdef TIMING + direction_time += gpuStopTimer(); //profiling + #endif + + int best = getCost(t_dir.getTexture(), t_dir.getAuxArray() ,numSamples); //find min cost. + #ifdef DEBUG + name.str(""); + name << "Final_Cost_Direction_fiber_"<< iter << "_" << iter_dir << ".bmp"; + test(t_dir.getTexture(), n_pixels*2.0, numSamples*n_pixels, name.str()); + iter_dir++; + #endif + stim::vec next( ///calculate the next vector. + dV[best][0]*S[0]*R[0], + dV[best][1]*S[1]*R[1], + dV[best][2]*S[2]*R[2], + 0); + next = (cT*next).norm(); ///transform the next vector into Tissue space. + setPosition( p[0]+next[0]*m/stepsize, + p[1]+next[1]*m/stepsize, + p[2]+next[2]*m/stepsize); + setDirection(next[0], next[1], next[2]); //move forward and change direction. + } + + /// Method for finding the best d (direction) for the spider. + /// Not sure if necessary since the next p (position) for the spider + /// will be at d * m. + void + findOptimalPosition() + { + #ifdef TIMING + gpuStartTimer(); //timer for profiling + #endif + setMatrix(); //create the transformation matrix. + glCallList(dList+1); //move the templates to p, d, m. + glFinish(); //flush the pipeline +// glFlush(); + #ifdef TIMING + position_time += gpuStopTimer(); ///timer for profiling + #endif + + int best = getCost(t_pos.getTexture(), t_pos.getAuxArray(), numSamplesPos); //find min cost. + #ifdef DEBUG + name.str(""); + name << "Final_Cost_Position_" << iter << "_" << iter_pos << ".bmp"; + test(t_pos.getTexture(), n_pixels*2.0, numSamplesPos*n_pixels, name.str()); + iter_pos++; + #endif + stim::vec next( //find next position. + pV[best][0], + pV[best][1], + pV[best][2], + 1); + next = cT*next; //transform the next position vector into tissue space. + setPosition( + next[0]*S[0]*R[0], + next[1]*S[1]*R[1], + next[2]*S[2]*R[2] + ); //adjust position. + } + + /// Method for finding the best scale for the spider. + /// changes the x, y, z size of the spider to minimize the cost + /// function. + void + findOptimalRadius() + { + #ifdef TIMING + gpuStartTimer(); + #endif + setMatrix(); //create the transformation. + glCallList(dList+2); //move the templates to p, d, m. + glFinish(); //flush the drawing pipeline. + #ifdef TIMING + size_time += gpuStopTimer(); + #endif + int best = getCost(t_mag.getTexture(), t_mag.getAuxArray(), numSamplesMag); //get best cost. + #ifdef DEBUG + name.str(""); + name << "Final_Cost_Size_" << iter << "_" << iter_siz << ".bmp"; + test(t_mag.getTexture(), n_pixels*2.0, numSamplesMag*n_pixels, name.str()); + iter_siz++; + #endif + setMagnitude(m*mV[best]); //adjust the magnitude. + } + + /// Method for finding the best length for the spider. + /// changes the x, y, z size of the spider to minimize the cost + /// function. + void + findOptimalLength() + { + #ifdef TIMING + gpuStartTimer(); + #endif + setMatrix(); //create the transformation. + glCallList(dList+3); //move the templates to p, d, m. + glFinish(); //flush the drawing pipeline. + #ifdef TIMING + size_time += gpuStopTimer(); + #endif + int best = getCost(t_len.getTexture(), t_len.getAuxArray(), numSamplesMag); //get best cost. + #ifdef DEBUG +// name.str(""); +// name << "Final_Cost_Size_" << iter << "_" << iter_siz << ".bmp"; +// test(t_mag.getTexture(), n_pixels*2.0, numSamplesMag*n_pixels, name.str()); +// iter_siz++; + #endif + setLength(mV[best]); //adjust the magnitude. + } + + + + + ///finds all the branches in the a given fiber. + ///using LoG method. + void + branchDetection2(int n = 8, int l_template = 8, int l_square = 8) + { + #ifdef TIMING + gpuStartTimer(); ///timer for performance analysis + #endif + + if(cL.size() < 4){} ///if the size of the fiber is less then 4 we do nothing. + else{ + setMatrix(1); ///finds the current transformation matrix + DrawLongCylinder(n, l_template, l_square); ///Draw the cylinder. + stim::cylinder cyl(cL, cM); + std::vector< stim::vec > result = find_branch(cylinder_texID, GL_TEXTURE_2D, n*l_square, (cL.size()-1)*l_template); ///find all the centers in cuda + + stim::vec3 size(S[0]*R[0], S[1]*R[1], S[2]*R[2]); ///the borders of the texture. + float pval; //pvalue associated with the points on the cylinder. + if(!result.empty()) ///if we have any points + { + for(int i = 0; i < result.size(); i++) ///for each point + { + int id = result[i][2]; + if(fmod(result[i][2], id) != 0 && id != 0) ///if the remainer is odd + { + + pval = ((cyl.getl(id+1)-cyl.getl(id))* + (fmod(result[i][2], id))+cyl.getl(id))/cyl.getl(cL.size()-1); ///calculate pvalue + } + else if(id == 0) ///if the point is on the edge + { + pval = (cyl.getl(id+1)*result[i][2])/cyl.getl(cL.size()-1); + } + else + { + pval = (cyl.getl(id)/cyl.getl(cL.size()-1)); ///if the point is somewhere on the surface of the cylinder other than the edge + } + stim::vec3 v = cyl.surf(pval, result[i][0]); ///find the coordinates of the point at pval on the surface in tissue space. + stim::vec3 di = cyl.p(pval); ///find the coord of v in tissue space projected on the centerline. + float rad = cyl.r(pval); ///find the radius at the pvalue's location + // float rad = cyl.r(pval)/2; ///find the radius at the pvalue's location + if( + !(v[0] > size[0] || v[1] > size[1] + || v[2] > size[2] || v[0] < 0 + || v[1] < 0 || v[2] < 0)) ///if the v point is INSIDE the volume + { + setSeed(v); ///add a seedpoint's position. + setSeedVec((v-di).norm()); ///add a seedpoints direction + setSeedMag(rad); ///add the starting radius. + } + } + } + } + #ifdef TIMING + branch_time += gpuStopTimer(); ///timer for performance. + #endif + } + + + float uniformRandom() + { + return ( (float)(rand()))/( (float)(RAND_MAX)); ///generates a random number between 0 and 1 using the uniform distribution. + } + + float normalRandom() + { + float u1 = uniformRandom(); + float u2 = uniformRandom(); + return cos(2.0*atan(1.0)*u2)*sqrt(-1.0*log(u1)); ///generate a random number using the normal distribution between 0 and 1. + } + + stim::vec3 uniformRandVector() + { + stim::vec3 r(uniformRandom(), uniformRandom(), 1.0); ///generate a random vector using the uniform distribution between 0 and 1. + return r; + } + + stim::vec3 normalRandVector() + { + stim::vec3 r(normalRandom(), normalRandom(), 1.0); ///generate a random vector using the normal distribution between 0 and 1. + return r; + } + + +//--------------------------------------------------------------------------// +//---------------------TEMPLATE CREATION METHODS----------------------------// +//--------------------------------------------------------------------------// + + + + ///@param solidAngle, the size of the arc to sample. + ///Method for populating the vector arrays with sampled vectors. + ///Objects created are rectangles the with the created directions. + ///All points are sampled from a texture. + ///Stored in a display list. + ///uses the default d vector <0,0,1> + void + genDirectionVectors(float solidAngle = 3*stim::PI/4) + { + + //Set up the vectors necessary for Rectangle creation. + stim::vec3 Y(1.0,0.0,0.0); //orthogonal vec. + stim::vec3 pos(0.0,0.0,0.0); //center point of a rectangle + float mag = 1.0; //size of the generated rectangle. + stim::vec3 dir(0.0, 0.0, 1.0); //normal of the rectangle + + float PHI[2], Z[2], range; + PHI[0] = solidAngle/2; ///Project the solid angle into spherical coordinates + PHI[1] = asin(0); + + Z[0] = cos(PHI[0]); ///Project the z into spherical coordinates + Z[1] = cos(PHI[1]); + + range = Z[0] - Z[1]; ///The range the possible values can be. + + float z, theta, phi; + glNewList(dList, GL_COMPILE); ///create a display list of all the direction templates. + for(int i = 0; i < numSamples; i++) ///for each sample + { + z = uniformRandom()*range + Z[1]; ///generate a z coordinate + theta = uniformRandom()*stim::TAU; ///generate a theta coordinate + phi = acos(z); ///generate a phi from the z. + stim::vec3 sph(1, theta, phi); ///combine into a vector in spherical coordinates. + stim::vec3 cart = sph.sph2cart();///convert to cartesian. + dV.push_back(cart); ///save the generated vector for further use. + #ifdef DEBUG +// std::cout << cart << std::endl; + #endif + if(cos(Y.dot(cart)) < 0.087) ///make sure that the Y is not parallel to the new vector. + { + Y[0] = 0.0; Y[1] = 1.0; + }else{ + Y[0] = 1.0; Y[1] = 0.0; + } + + hor = stim::rect(mag, ///generate a rectangle with the new vectro as a normal. + pos, cart, + ((Y.cross(cart)).cross(cart)).norm()); + + #ifdef DEBUG + // std::cout << hor.n() << std::endl; + #endif + ver = stim::rect(mag, ///generate another rectangle that's perpendicular the first but parallel to the cart vector. + pos, cart, + hor.n()); + UpdateBuffer(0.0, 0.0+i*n_pixels); ///Put the necessary points into the diplaylist. + } + glEndList(); ///finilize the display list. + } + + ///@param float delta, How much the rectangles vary in position. + ///Method for populating the buffer with the sampled texture. + ///Objects created are rectangles the with the created positions. + ///All points are sampled from a texture. + ///Stored in a display list. + ///uses the default vector <0,0,0> + void + genPositionVectors(float delta = 0.4) + { + //Set up the vectors necessary for Rectangle creation. + stim::vec3 Y(1.0,0.0,0.0); //orthogonal vec. + stim::vec3 pos(0.0,0.0,0.0); //center point of a rectangle + float mag = 1.0; ///size of each rectangle + stim::vec3 dir(0.0, 0.0, 1.0); ///normal of the rectangle plane. + + //Set up the variable necessary for vector creation. + glNewList(dList+1, GL_COMPILE); ///generate a new display list. + pV.push_back(pos); + hor = stim::rect(mag, ///generate a rec tangle with the new vector as a normal. + pos, dir, + ((Y.cross(d)).cross(d)) + .norm()); + ver = stim::rect(mag, ///generate anoth er rectangle that's perpendicular the first but parallel to the cart vector. + pos, dir, + hor.n()); + ///The first vector is always in the center. + UpdateBuffer(0.0, 0.0+0*n_pixels); + for(int i = 1; i < numSamplesPos; i++) ///for the number of position samples + { + stim::vec3 temp = uniformRandVector(); ///generate a random point on a plane. + temp[0] = temp[0]*delta; + temp[1] = temp[1]*2*stim::PI; + + temp[2] = 0.0; + temp = temp.cyl2cart(); + pV.push_back(temp); ///save the point for further use. + hor = stim::rect(mag, ///generate a rectangle with the new vector as a normal. + temp, dir, + ((Y.cross(d)).cross(d)) + .norm()); + ver = stim::rect(mag, ///generate another rectangle that's perpendicular the first but parallel to the cart vector. + temp, dir, + hor.n()); + UpdateBuffer(0.0, 0.0+i*n_pixels); ///sample the necessary points and put them into a display list. + } + glEndList(); ///finilize the display list. + #ifdef DEBUG + for(int i = 0; i < numSamplesPos; i++) + std::cout << pV[i].str() << std::endl; + #endif + } + + ///@param float delta, How much the rectangles are allowed to expand. + ///Method for populating the buffer with the sampled texture. + ///Objects created are rectangles the with the created sizes. + ///All points are sampled from a texture. + ///Stored in a display list. + ///uses the default m <1,1,0> + void + genMagnitudeVectors(float delta = 0.70) + { + + //Set up the vectors necessary for Rectangle creation. + stim::vec3 Y(1.0, 0.0, 0.0); //orthogonal vec. + stim::vec3 pos(0.0, 0.0, 0.0); //center of the future rect. + float mag = 1.0; ///size of the rectangle + stim::vec3 dir(0.0, 0.0, 1.0); ///normal of the rectangle plane. + + //Set up the variable necessary for vector creation. + float min = 1.0-delta; ///smallest size + float max = 1.0+delta; ///largers size. + float step = (max-min)/(numSamplesMag-1); ///the size variation from one rect to the next. + float factor; + glNewList(dList+2, GL_COMPILE); + for(int i = 0; i < numSamplesMag; i++){ ///for the number of position samples + //Create linear index + factor = (min+step*i)*mag; ///scaling factor + mV.push_back(factor); ///save the size factor for further use. + hor = stim::rect(factor, ///generate a rectangle with the new vector as a normal. + pos, dir, + ((Y.cross(d)).cross(d)) + .norm()); + ver = stim::rect(factor, ///generate another rectangle that's perpendicular the first but parallel to the cart vector. + pos, dir, + hor.n()); + UpdateBuffer(0.0, 0.0+i*n_pixels); ///sample the necessary points and put them into a display list. + CHECK_OPENGL_ERROR + } + glEndList(); ///finilize the displaylist. + } + + ///@param float delta, How much the rectangles are allowed to expand. + ///Method for populating the buffer with the sampled texture. + ///Objects created are rectangles the with the created sizes. + ///All points are sampled from a texture. + ///Stored in a display list. + ///uses the default m <1,1,0> + void + genLengthVectors(float delta = 0.70) + { + + //Set up the vectors necessary for Rectangle creation. + stim::vec3 Y(1.0, 0.0, 0.0); //orthogonal vec. + stim::vec3 pos(0.0, 0.0, 0.0); //center of the future rect. + float mag = 1.0; ///size of the rectangle + stim::vec3 dir(0.0, 0.0, 1.0); ///normal of the rectangle plane. + stim::vec temp(0.0,0.0,0.0); + + //Set up the variable necessary for vector creation. + float min = 1.0-delta; ///smallest size + float max = 1.0+delta; ///largers size. + float step = (max-min)/(numSamplesMag-1); ///the size variation from one rect to the next. + float factor; + glNewList(dList+3, GL_COMPILE); + for(int i = 0; i < numSamplesMag; i++){ ///for the number of position samples + //Create linear index + factor = (min+step*i)*mag; ///scaling factor + lV.push_back(factor); ///save the size factor for further use. + temp[0] = factor; + temp[1] = mag; + hor = stim::rect(temp, ///generate a rectangle with the new vector as a normal. + pos, dir, + ((Y.cross(d)).cross(d)) + .norm()); + ver = stim::rect(temp, ///generate another rectangle that's perpendicular the first but parallel to the cart vector. + pos, dir, + hor.n()); + UpdateBuffer(0.0, 0.0+i*n_pixels); ///sample the necessary points and put them into a display list. + CHECK_OPENGL_ERROR + } + glEndList(); ///finilize the displaylist. + } + + ///@param float v_x x-coordinate in buffer-space, + ///@param float v_y y-coordinate in buffer-space. + ///Samples the texture space. + ///places a sample in the provided coordinates of bufferspace. + void + UpdateBuffer(float v_x, float v_y) + { + stim::vec3p1; ///first point. + stim::vec3p2; ///second point. + stim::vec3p3; ///third point. + stim::vec3p4; ///fourth point. + p1 = hor.p(1,1); ///generate the top right point from the horizontal template. + p2 = hor.p(1,0); ///generate the bottom right point from the horizonatal template. + p3 = hor.p(0,0); ///generate the bottom left point from the horizontal template. + p4 = hor.p(0,1); ///generate the top left point from the horizonatal template. + glBegin(GL_QUADS); ///generate the Quad from the 4 points. + glTexCoord3f( + p1[0], + p1[1], + p1[2] + ); + glVertex2f(v_x,v_y); + glTexCoord3f( + p2[0], + p2[1], + p2[2] + ); + glVertex2f(v_x+n_pixels, v_y); + glTexCoord3f( + p3[0], + p3[1], + p3[2] + ); + glVertex2f(v_x+n_pixels, v_y+n_pixels); + glTexCoord3f( + p4[0], + p4[1], + p4[2] + ); + glVertex2f(v_x, v_y+n_pixels); + glEnd(); ///finish the quad. + + p1 = ver.p(1,1); ///generate the top right point from the vertical template. + p2 = ver.p(1,0); ///generate the bottom right point from the vertical template. + p3 = ver.p(0,0); ///generate the bottom left point from the vertical template. + p4 = ver.p(0,1); ///generate the top left point from the vertical template. + glBegin(GL_QUADS); ///generate the Quad from the 4 points. + glTexCoord3f( + p1[0], + p1[1], + p1[2] + ); + glVertex2f(v_x+n_pixels, v_y); + glTexCoord3f( + p2[0], + p2[1], + p2[2] + ); + glVertex2f(v_x+2.0*n_pixels, v_y); + glTexCoord3f( + p3[0], + p3[1], + p3[2] + ); + glVertex2f(v_x+2.0*n_pixels, v_y+n_pixels); + glTexCoord3f( + p4[0], + p4[1], + p4[2] + ); + glVertex2f(v_x+n_pixels, v_y+n_pixels); + glEnd(); ///finish the quad. + } + + + +//--------------------------------------------------------------------------// +//--------------------------------GL METHODS--------------------------------// +//--------------------------------------------------------------------------// + + ///@param uint width sets the width of the buffer. + ///@param uint height sets the height of the buffer. + ///@param GLuint &textureID gives the texture ID of the texture to be initialized. + ///@param GLuint &framebufferID gives the buffer ID of the texture to be initialized. + ///Function for setting up the 2D buffer that stores the samples. + ///Initiates and sets parameters. + void + GenerateFBO(unsigned int width, unsigned int height, GLuint &textureID, GLuint &framebufferID) + { + glDeleteFramebuffers(1, &framebufferID); ///clear the framebuffer. + glGenFramebuffers(1, &framebufferID); ///generate a clean buffer. + glBindFramebuffer(GL_FRAMEBUFFER, framebufferID); ///bind the new buffer. +// int numChannels = 1; +// unsigned char* texels = new unsigned char[width * height * numChannels]; + glGenTextures(1, &textureID); ///generate a texture that will attach to the buffer. + glBindTexture(GL_TEXTURE_2D, textureID); + + //Textures repeat and use linear interpolation, luminance format. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); ///Set up the texture to repeat at edges. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); ///Set up the texture to use Linear interpolation + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, ///Create the texture with no data. + width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL); +// delete[] texels; + glBindFramebuffer(GL_FRAMEBUFFER, 0); ///Bind the frontbuffer + glBindTexture(GL_TEXTURE_2D, 0); ///Unbind the texture. + } + + + ///IF type == 0 + ///Method for using the gl manipulation to align templates from + ///Template space (-0.5 0.5) to Texture space (0.0, 1.0), + ///Based on the p of the spider in real space (arbitrary). + + ///IF type == 1 + ///Method for using the gl manipulation to set up a matrix + ///To transform from tissue space into texture space. + ///All transformation happen in glMatrixMode(GL_TEXTURE). + ///All transformation happen in glMatrixMode(GL_TEXTURE). + void setMatrix(int type = 0) + { + if(type == 0) + { + float curTrans[16]; //array to store the matrix values. + stim::vec rot = getRotation(d); //get the rotation parameters for the current direction vector. + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + + //Scale by the voxel size and number of slices. + glScalef(1.0/S[0]/R[0], 1.0/S[1]/R[1], 1.0/S[2]/R[2]); + //translate to the current position of the spider in the texture. + glTranslatef(p[0], + p[1], + p[2]); + //rotate to the current direction of the spider. + glRotatef(rot[0], rot[1], rot[2], rot[3]); + //scale to the magnitude of the spider. + glScalef(m, + m, + m); + //get and store the current transformation matrix for later use. + glGetFloatv(GL_TEXTURE_MATRIX, curTrans); + cT.set(curTrans); + + CHECK_OPENGL_ERROR + //revert back to default gl mode. + glMatrixMode(GL_MODELVIEW); + } + else if(type == 1) + { + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glScalef(1.0/S[0]/R[0], 1.0/S[1]/R[1], 1.0/S[2]/R[2]); + glMatrixMode(GL_MODELVIEW); + } + } + + ///Method for controling the buffer and texture binding. + ///Clears the buffer upon binding. + void + Bind() + { + glBindFramebuffer(GL_FRAMEBUFFER, direction_buffID);//set up GL buffer + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + direction_texID, + 0); ///Bind the texture to the 0th color attachement of the framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, direction_buffID); ///Bind the buffer again (safety operation). + GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; ///Designate the texture to be the drawbuffer of the framebuffer + glDrawBuffers(1, DrawBuffers); ///Set the current drawbuffer to the texture. + glBindTexture(GL_TEXTURE_2D, direction_texID); ///Bind the Texture + glClearColor(1,1,1,1); ///Set clear color to white + glClear(GL_COLOR_BUFFER_BIT); ///Clear the texture + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); ///Load identity matrix into the projection and modelview + glViewport(0,0,2.0*n_pixels, numSamples*n_pixels); ///Designate the viewport and orth + gluOrtho2D(0.0,2.0*n_pixels,0.0,numSamples*n_pixels); + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, texID); ///Bind the larger 3D texture. + + CHECK_OPENGL_ERROR + } + + ///Method for controling the buffer and texture binding. + ///Clears the buffer upon binding. + ///@param GLuint &textureID, texture to be bound. + ///@param GLuint &framebufferID, framebuffer used for storage. + ///@param int nSamples, number of rectanges to create. + void + Bind(GLuint &textureID, GLuint &framebufferID, int nSamples, float len = 8.0) + { + + glBindFramebuffer(GL_FRAMEBUFFER, framebufferID); ///Bind the framebuffer. + glFramebufferTexture2D( ///associate it with the texture + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + textureID, + 0); + glBindFramebuffer(GL_FRAMEBUFFER, framebufferID); ///Bind the framebuffer. + GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}; ///generate the drawbuffer. + glDrawBuffers(1, DrawBuffers); ///set the drawbuffer. + glBindTexture(GL_TEXTURE_2D, textureID); ///Bind the texture passed. + glMatrixMode(GL_PROJECTION); ///clear out the draw matrices + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glViewport(0,0,2.0*len, nSamples*len); ///set up viewport + gluOrtho2D(0.0,2.0*len,0.0,nSamples*len); ///Set up ortho + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, texID); ///bind the main texture (return to original state). + + CHECK_OPENGL_ERROR + } + + ///Unbinds all texture resources. + void + Unbind() + { + //Finalize GL_buffer + glBindTexture(GL_TEXTURE_3D, 0); ///Bind the front buffer. + CHECK_OPENGL_ERROR + glBindTexture(GL_TEXTURE_2D, 0); ///Bind the default GL texture. + CHECK_OPENGL_ERROR + glBindFramebuffer(GL_FRAMEBUFFER, 0); ///Bind the defautl framebuffer. + CHECK_OPENGL_ERROR + glDisable(GL_TEXTURE_3D); ///Turn off texturing. + CHECK_OPENGL_ERROR + } + + + + +//--------------------------------------------------------------------------// +//--------------------------------CUDA METHODS------------------------------// +//--------------------------------------------------------------------------// + + + ///Entry-point into the cuda code for calculating the cost of a given samples array (in texture form) + ///finds the minimum cost and sets the current_cost to that value. + /// and returns the index of the template with the minimal cost. + int + getCost(cudaTextureObject_t tObj, float* result, int n) + { + #ifdef TIMING + gpuStartTimer(); ///Add timing variables + #endif + stim::vec cost = + stim::cuda::get_cost(tObj, result, n, 2*n_pixels, n_pixels); ///call the cuda function with the appropriate texture buffer. + #ifdef TIMING + cost_time += gpuStopTimer(); + #endif + current_cost = cost[1]; ///current cost. + return cost[0]; + } + + public: + + ///ininializes the cuda device and environment. + void + initCuda() + { + stim::cudaSetDevice(); + } + + //horizonal rectangle forming the spider. + stim::rect hor; + //vectical rectangle forming the spider. + stim::rect ver; + + //Timing variables. + +//--------------------------------------------------------------------------// +//-----------------------------CONSTRUCTORS---------------------------------// +//--------------------------------------------------------------------------// + + + ///@param int samples, the number of samples this spider is going to use. + ///Best results if samples is can create a perfect root. + ///Default Constructor + gl_spider + (int samples = 1089, int samplespos = 441,int samplesmag = 144) + { + p = stim::vec3(0.0, 0.0, 0.0); + d = stim::vec3(0.0, 0.0, 1.0); + m = 1.0; + S = stim::vec3(1.0, 1.0, 1.0); + R = stim::vec3(1.0, 1.0, 1.0); + numSamples = samples; + numSamplesPos = samplespos; + numSamplesMag = samplesmag; + #ifdef DEBUG + iter = 0; + iter_pos = 0; + iter_dir = 0; + iter_siz = 0; + #endif + } + + ///Position constructor: floats. + ///@param float pos_x, position x. + ///@param float pos_y, position y. + ///@param float pos_z, position z. + ///@param float dir_x, direction x. + ///@param float dir_y, direction y. + ///@param float dir_z, direction z. + ///@param float mag_x, size of the vector. + ///@param int samples, number of templates this spider is going to use. + gl_spider + (float pos_x, float pos_y, float pos_z, float dir_x, float dir_y, float dir_z, + float mag_x, int numsamples = 1089, int numsamplespos = 441, int numsamplesmag =144) + { + p = stim::vec3(pos_x, pos_y, pos_z); + d = stim::vec3(dir_x, dir_y, dir_z); + m = mag_x; + S = stim::vec3(1.0,1.0,1.0); + R = stim::vec3(1.0,1.0,1.0); + numSamples = numsamples; + numSamplesPos = numsamplespos; + numSamplesMag = numsamplesmag; + #ifdef DEBUG + iter = 0; + iter_pos = 0; + iter_dir = 0; + iter_siz = 0; + #endif + } + + ///Position constructor: vecs of floats. + ///@param stim::vec pos, position. + ///@param stim::vec dir, direction. + ///@param float mag, size of the vector. + ///@param int samples, number of templates this spider is going to use. + gl_spider + (stim::vec3 pos, stim::vec3 dir, float mag, int samples = 1089, int samplesPos = 441, int samplesMag = 144) + { + p = pos; + d = dir; + m = mag; + S = vec3(1.0,1.0,1.0); + R = vec3(1.0,1.0,1.0); + numSamples = samples; + numSamplesPos = samplesPos; + numSamplesMag = samplesMag; + #ifdef DEBUG + iter = 0; + iter_pos = 0; + iter_dir = 0; + iter_siz = 0; + #endif + } + + ///destructor deletes all the texture and buffer objects. + ~gl_spider + (void) + { + Unbind(); + glDeleteTextures(1, &direction_texID); + glDeleteBuffers(1, &direction_buffID); + glDeleteTextures(1, &position_texID); + glDeleteBuffers(1, &position_buffID); + glDeleteTextures(1, &radius_texID); + glDeleteBuffers(1, &radius_buffID); + glDeleteTextures(1, &length_texID); + glDeleteBuffers(1, &length_buffID); + glDeleteTextures(1, &cylinder_texID); + glDeleteBuffers(1, &cylinder_buffID); + } + + ///@param GLuint id, texture that is going to be sampled. + ///Attached the spider to the texture with the given GLuint ID. + ///Samples in the default d acting as the init method. + ///Also acts an init. + void + attachSpider(GLuint id) + { +#ifdef TIMING + branch_time = 0; + direction_time = 0; + position_time = 0; + size_time = 0; + cost_time = 0; + network_time = 0; + hit_time = 0; +#endif +#ifdef DEBUG + iter = 0; + iter_pos = 0; + iter_dir = 0; + iter_siz = 0; +#endif + stepsize = 6.0; + n_pixels = 16.0; + + srand(100); + texID = id; + GenerateFBO(n_pixels*2, numSamples*n_pixels, direction_texID, direction_buffID); + CHECK_OPENGL_ERROR + GenerateFBO(n_pixels*2, numSamplesPos*n_pixels, position_texID, position_buffID); + CHECK_OPENGL_ERROR + GenerateFBO(n_pixels*2, numSamplesMag*n_pixels, radius_texID, radius_buffID); + CHECK_OPENGL_ERROR + GenerateFBO(n_pixels*2, numSamplesMag*n_pixels, length_texID, length_buffID); + CHECK_OPENGL_ERROR + GenerateFBO(16, 216, cylinder_texID, cylinder_buffID); + CHECK_OPENGL_ERROR + t_dir.MapCudaTexture(direction_texID, GL_TEXTURE_2D); + t_dir.Alloc(numSamples); + t_pos.MapCudaTexture(position_texID, GL_TEXTURE_2D); + t_pos.Alloc(numSamplesPos); + t_mag.MapCudaTexture(radius_texID, GL_TEXTURE_2D); + t_mag.Alloc(numSamplesMag); + t_len.MapCudaTexture(length_texID, GL_TEXTURE_2D); + t_len.Alloc(numSamplesMag); + setMatrix(); + dList = glGenLists(4); + glListBase(dList); + Bind(direction_texID, direction_buffID, numSamples, n_pixels); + genDirectionVectors(5*stim::PI/4); + Unbind(); + Bind(position_texID, position_buffID, numSamplesPos, n_pixels); + genPositionVectors(0.2); + Unbind(); + Bind(radius_texID, radius_buffID, numSamplesMag, n_pixels); + genMagnitudeVectors(); + Unbind(); + Bind(length_texID, length_buffID, numSamplesMag, n_pixels); + genLengthVectors(); + Unbind(); + } + +//--------------------------------------------------------------------------// +//-----------------------------ACCESS METHODS-------------------------------// +//--------------------------------------------------------------------------// + ///Returns the p vector. + vec3 + getPosition() + { + return p; + } + + ///Returns the d vector. + vec3 + getDirection() + { + return d; + } + + ///Returns the m vector. + float + getMagnitude() + { + return m; + } + + ///@param stim::vec pos, the new p. + ///Sets the p vector to input vector pos. + void + setPosition(stim::vec3 pos) + { + p = pos; + } + + ///@param float x x-coordinate. + ///@param float y y-coordinate. + ///@param float z z-coordinate. + ///Sets the p vector to the input float coordinates x,y,z. + void + setPosition(float x, float y, float z) + { + p[0] = x; + p[1] = y; + p[2] = z; + } + + ///@param stim::vec dir, the new d. + ///Sets the d vector to input vector dir. + void + setDirection(stim::vec3 dir) + { + d = dir; + } + + ///@param stim::vec x x-coordinate. + ///@param stim::vec y y-coordinate. + ///@param stim::vec z z-coordinate. + ///Sets the d vector to the input float coordinates x,y,z. + void + setDirection(float x, float y, float z) + { + d[0] = x; + d[1] = y; + d[2] = z; + } + + + ///@param float mag, size of the sampled region. + ///Sets the m value to the input mag for both templates. + void + setMagnitude(float mag) + { + m = mag; + } + + ///@param float len, length of the sampled region. + ///Sets the length value to the input len for both templates. + void + setLength(float len) + { + length = len; + } + + ///@param float x, voxel size in the x direction. + ///@param float y, voxel size in the y direction. + ///@param float z, voxel size in the z direction. + ///Sets the voxel sizes in each direction. necessary for non-standard data. + void + setDims(float x, float y, float z) + { + S[0] = x; + S[1] = y; + S[2] = z; + } + + ///@param stim::vec Dims, voxel size. + ///Sets the voxel sizes in each direction. necessary for non-standard data. + void + setDims(stim::vec3 Dims) + { + S = Dims; + } + + ///@param float x, size of the data in the x direction. + ///@param float y, size of the data in the y direction. + ///@param float z, size of the data in the z direction. + ///Sets the data volume sizes in each direction. + void + setSize(float x, float y, float z) + { + R[0] = x; + R[1] = y; + R[2] = z; + } + + ///@param stim::vec Dims, size of the volume. + ///Sets the data volume sizes in each direction. + void + setSize(stim::vec3 Siz) + { + S = Siz; + } + + ///@param stim::vec dir, the vector to which we are rotating. + ///given a vector to align to, finds the required axis and angle for glRotatef. + ///rotates from 0.0, 0.0, 1.0 to dir. + ///return is in degrees for use with glRotatef. + stim::vec + getRotation(stim::vec3 dir) + { + stim::vec out(0.0,0.0,0.0,0.0); ///The 4D rotation matrix for GL rotation + stim::vec3 from(0.0,0.0,1.0); ///Converting from template always involves 0,0,1 as a starting vector + out[0] = acos(dir.dot(from))*180/stim::PI; ///angle of rotation + if(out[0] < 1.0){ + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 1.0; + } ///if we rotate less what one degree don't rotate + else if(out[0] < -179.0) ///if we rotate more than -179 degrees rotate 180. + { + out[0] = 180.0; + out[1] = 1.0; + out[2] = 0.0; + out[3] = 0.0; + } else { ///the rotational axis is the cross fromxdir. + stim::vec3 temp(0.0, 0.0, 0.0);; + temp = (from.cross(dir)).norm(); + out[1] = temp[0]; + out[2] = temp[1]; + out[3] = temp[2]; + } + #ifdef DEBUG + std::cout << "out is " << out.str() << std::endl; + std::cout << "when rotating from " << from.str() << " to " << dir.str() << std::endl; + #endif + return out; + } + + ///@param stim::vec pos, the position of the seed to be added. + ///Adds a seed to the seed list. + ///Assumes that the coordinates passes are in tissue space. + void + setSeed(stim::vec3 pos) + { + seeds.push(pos); + } + + ///@param stim::vec dir, the direction of the seed to be added. + ///Adds a seed to the seed directions list. + void + setSeedVec(stim::vec3 dir) + { + seedsvecs.push(dir); + } + + ///@param float mag, the size of the seed to be added. + ///Adds a seed to the seed list. + ///Assumes that the coordinates passes are in tissue space. + void + setSeedMag(float mag) + { + seedsmags.push(mag); + } + + + ///@param float x, x-position of the seed to be added. + ///@param float y, y-position of the seed to be added. + ///@param float z, z-position of the seed to be added. + ///Adds a seed to the seed list. + ///Assumes that the coordinates passes are in tissue space. + void + setSeed(float x, float y, float z) + { + seeds.push(stim::vec(x, y, z)); + } + + ///@param float x, x-direction of the seed to be added. + ///@param float y, y-direction of the seed to be added. + ///@param float z, z-direction of the seed to be added. + ///Adds a seed to the seed directions list. + void + setSeedVec(float x, float y, float z) + { + seedsvecs.push(stim::vec(x, y, z)); + } + + ///Method to get the top of the seed positions stack. + stim::vec3 + getLastSeed() + { + stim::vec3 tp = seeds.top(); + return tp; + } + + ///Method to get the top of the seed direction stack. + stim::vec3 + getLastSeedVec() + { + stim::vec3 tp = seedsvecs.top(); + return tp; + } + + ///Method to get the top of the seed magnitude stack. + float + getLastSeedMag() + { + float tp = seedsmags.top(); + return tp; + } + + ///deletes all data associated with the last seed. + void + popSeed() + { + seeds.pop(); + seedsvecs.pop(); + seedsmags.pop(); + } + + ///returns the seeds position stack. + std::stack > + getSeeds() + { + return seeds; + } + + ///sets the number of direction templates. + void + setNumberDirectionTemplates(int n) + { + numSamples = n; + } + + ///sets the number of position templates. + void + setNumberPositionTemplates(int n) + { + numSamplesPos = n; + } + + ///sets the number of position templates. + void + setNumberSizeTemplates(int n) + { + numSamplesMag = n; + } + + #ifdef TIMING + ///Returns the timings at the moment the method is called. + ///In the following order: Branch, Direction, Position, Size, Cost, Network, Hit_detetion. + std::vector + getTimings() + { + std::vector ret; + ret.resize(7); + ret[0] = branch_time; + ret[1] = direction_time; + ret[2] = position_time; + ret[3] = size_time; + ret[4] = cost_time; + ret[5] = network_time; + ret[6] = hit_time; + + return ret; + } + #endif + + ///returns true if all seed stacks are empty, else false. + bool + Empty() + { + //return (seeds.empty() && seedsvecs.empty() && seedsmags.empty()); + return (seeds.empty() && seedsvecs.empty()); + } + + ///@param std::string file:file with variables to populate the seed stacks. + ///Adds a seed to the seed list, including the position, direction and magnitude. + ///Assumes that the coordinates passes are in tissue space. + void + setSeeds(std::string file) + { + std::ifstream myfile(file.c_str()); ///open a stream + string line; + if(myfile.is_open()) + { + while (getline(myfile, line)) + { + float x, y, z, u, v, w, m; ///read the xyz uvw and m coordinates. + myfile >> x >> y >> z >> u >> v >> w >> m; + setSeed(x, y, z); + setSeedVec(u, v, w); + setSeedMag(m); + } + myfile.close(); + } else { + std::cerr<<"failed" << std::endl; + } + } + + ///Saves the network to a file. + void + saveNetwork(std::string name, int xoffset = 0, int yoffset = 0, int zoffset = 0) + { + stim::glObj sk1; + for(int i = 0; i < nt.sizeE(); i++) + { + std::vector cm = nt.getEdgeCenterLineMag(i); + std::vector > ce = nt.getEdgeCenterLine(i); + sk1.Begin(stim::OBJ_LINE); + for(int j = 0; j < ce.size(); j++) + { + sk1.TexCoord(cm[j]); + sk1.Vertex(ce[j][0]+xoffset, ce[j][1]+yoffset, ce[j][2]+zoffset); + } + sk1.End(); + } + sk1.save(name); + } + + ///Depreciated, but might be reused later() + ///returns a COPY of the entire stim::glObj object. + stim::glObj + getNetwork() + { + return sk; + } + + ///returns a COPY of the entire stim::glnetwork object. + stim::gl_network + getGLNetwork() + { + return nt; + } + + ///Function to get back the framebuffer Object attached to the spider. + ///For external access. + GLuint + getFB() + { + return cylinder_buffID; + } + +//--------------------------------------------------------------------------// +//-----------------------------TEMPORARY METHODS----------------------------// +//--------------------------------------------------------------------------// + + ///temporary Method necessary for visualization and testing. + void + Update() + { + vec3 Y(1.0,0.0,0.0); + if(cos(Y.dot(d))< 0.087){ + Y[0] = 0.0; Y[1] = 1.0;} + hor = stim::rect(m, p, d.norm(), + ((Y.cross(d)).cross(d)).norm()); + ver = stim::rect(m, p, d.norm(), + hor.n()); + } + + + int + Step() + { + #ifdef DEBUG + std::cerr << "Took a step"; + #endif + Bind(direction_texID, direction_buffID, numSamples, n_pixels); + CHECK_OPENGL_ERROR + findOptimalDirection(); + Unbind(); + #ifdef DEBUG + std::cerr << " " << current_cost; + #endif + Bind(position_texID, position_buffID, numSamplesPos, n_pixels); + findOptimalPosition(); + Unbind(); + #ifdef DEBUG + std::cerr << " " << current_cost; + #endif + Bind(radius_texID, radius_buffID, numSamplesMag, n_pixels); + findOptimalRadius(); + Unbind(); + #ifdef DEBUG + std::cerr << " " << current_cost; + #endif + CHECK_OPENGL_ERROR + #ifdef DEBUG + std::cerr << std::endl; + #endif + return current_cost; + } + + + void + printTransform() + { + std::cout << cT << std::endl; + } + + +//--------------------------------------------------------------------------// +//-----------------------------EXPERIMENTAL METHODS-------------------------// +//--------------------------------------------------------------------------// + + void + MonteCarloDirectionVectors(int nSamples, float solidAngle = stim::TAU) + { +// float PHI[2];//, Z[2];//, range; +// PHI[0] = asin(solidAngle/2); +// PHI[1] = asin(0); + +// Z[0] = cos(PHI[0]); +// Z[1] = cos(PHI[1]); + +// range = Z[0] - Z[1]; + + std::vector > vecsUni; + for(int i = 0; i < numSamplesPos; i++) + { + stim::vec3 a(uniformRandom()*0.8, uniformRandom()*0.8, 0.0); + a[0] = a[0]-0.4; + a[1] = a[1]-0.4; + vecsUni.push_back(a); + } + + stringstream name; + for(int i = 0; i < numSamplesPos; i++) + name << vecsUni[i].str() << std::endl; + + std::ofstream outFile; + outFile.open("New_Pos_Vectors.txt"); + outFile << name.str().c_str(); + } +/* + void + DrawCylinder() + { + glNewList(dList+3, GL_COMPILE); + float z0 = -0.5; float z1 = 0.5; float r0 = 0.5; + float x,y; + float xold = 0.5; float yold = 0.0; + float step = 360.0/numSamples*32; + //float step = 360.0/8.0; + glEnable(GL_TEXTURE_3D); + glBindTexture(GL_TEXTURE_3D, texID); + glBegin(GL_QUAD_STRIP); + int j = 0; + for(float i = step; i <= 360.0; i += step) + { + x=r0*cos(i*stim::TAU/360.0); + y=r0*sin(i*stim::TAU/360.0); + glTexCoord3f(x,y,z0); + glVertex2f(0.0, j*6.4+6.4); +// glVertex2f(0.0, j*27.0+27.0); + glTexCoord3f(x,y,z1); + glVertex2f(16.0, j*6.4+6.4); +// glVertex2f(16.0, j*27.0+27.0); + glTexCoord3f(xold,yold,z1); + glVertex2f(16.0, j*6.4); +// glVertex2f(16.0, j*27.0); + glTexCoord3f(xold,yold,z0); + glVertex2f(0.0, j*6.4); +// glVertex2f(0.0, j*27.0); + xold=x; + yold=y; + j++; + } + glEnd(); + glEndList(); + } +*/ +///need to return the cylinder. +///SOMETHING MIGHT BE GOING ON HERE IN GENERATE BUFFER. + void + DrawLongCylinder(int n = 8, int l_template = 8,int l_square = 8) + { + int cylLen = cL.size()-1; + GenerateFBO(n*l_square, cylLen*l_template, cylinder_texID, cylinder_buffID); + Bind(cylinder_texID, cylinder_buffID, cylLen, l_template*l_square/2.0); + stim::cylinder cyl(cL, cM); + std::vector > > p = cyl.getPoints(n); + for(int i = 0; i < p.size()-1; i++) ///number of circles + { + for(int j = 0; j < p[0].size()-1; j++) ///points in the circle + { + glBegin(GL_QUADS); + glTexCoord3f(p[i][j][0], p[i][j][1], p[i][j][2]); + glVertex2f(j*l_square, i*(float)l_template); + + glTexCoord3f(p[i][j+1][0], p[i][j+1][1], p[i][j+1][2]); + glVertex2f(j*l_square+l_square, i*(float)l_template); + + glTexCoord3f(p[i+1][j+1][0], p[i+1][j+1][1], p[i+1][j+1][2]); + glVertex2f(j*l_square+l_square, i*(float)l_template+(float)l_template); + + glTexCoord3f(p[i+1][j][0], p[i+1][j][1], p[i+1][j][2]); + glVertex2f(j*l_square,i*(float)l_template+(float)l_template); + glEnd(); + } + } + Unbind(); + } + + + ///@param min_cost the cost value used for tracing + ///traces out each seedpoint in the seeds queue to completion in both directions. + void + trace(int min_cost) + { + stim::vec3 curSeed; + stim::vec3 curSeedVec; + float curSeedMag; + while(!Empty()) + { + //clear the currently traced line and start a new one. + curSeed = seeds.top(); + curSeedVec = seedsvecs.top(); + curSeedMag = seedsmags.top(); + seeds.pop(); + seedsvecs.pop(); + seedsmags.pop(); + setPosition(curSeed); + setDirection(curSeedVec); + setMagnitude(curSeedMag); + + #ifdef DEBUG + std::cout << "The new seed " << curSeed.str() << curSeedVec.str() << curSeedMag << std::endl; + #endif + +// Bind(direction_texID, direction_buffID, numSamples, n_pixels); +// CHECK_OPENGL_ERROR +// findOptimalDirection(); +// Unbind(); +//THIS IS EXPERIMENTAL + // Bind(radius_texID, radius_buffID, numSamplesMag, n_pixels); + // findOptimalRadius(); + // Unbind(); +//THIS IS EXPERIMENTAL + +// cL.push_back(curSeed); +// cM.push_back(curSeedMag); +// cD.push_back(curSeedMag); + traceLine(p, m, min_cost); + } + } + + int + selectObject(stim::vec3 loc, stim::vec3 dir, float mag) + { + //Define the varibles and turn on Selection Mode + + #ifdef TIMING + gpuStartTimer(); + #endif + + GLuint selectBuf[2048]; ///size of the selection buffer in bytes. + GLint hits; ///hit fibers + glSelectBuffer(2048, selectBuf); ///bind the selection mode to the selection buffer. + glDisable(GL_CULL_FACE); ///Disable cullFace + (void) glRenderMode(GL_SELECT); ///initialize GL select mode. + //Init Names stack + + glInitNames(); ///Initialize the naming array. + glPushName(1); ///Push a single name to the stack. + + CHECK_OPENGL_ERROR + //What would that vessel see in front of it. + camSel.setPosition(loc); ///Set the viewing camera + camSel.setFocalDistance(mag/stepsize); ///Set how far the fiber looks forward. + camSel.LookAt((loc[0]+dir[0]*mag/stepsize), + (loc[1]+dir[1]*mag/stepsize), + (loc[2]+dir[2]*mag/stepsize)); ///Set the look direction + ps = camSel.getPosition(); ///get all the necessary rotation variable for openGL + ups = camSel.getUp(); + ds = camSel.getLookAt(); + glMatrixMode(GL_PROJECTION); ///Push the projection matrix. + glPushMatrix(); ///Reset the current projection matrix + glLoadIdentity(); + glOrtho(-mag/stepsize/2.0, mag/stepsize/2.0, -mag/stepsize/2.0, mag/stepsize/2.0, 0.0, mag/stepsize/2.0); ///Finalize the look paramenters + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + CHECK_OPENGL_ERROR + gluLookAt(ps[0], ps[1], ps[2], + ds[0], ds[1], ds[2], + ups[0], ups[1], ups[2]); + ///Set the look at distance +// sk.Render(); ///Render the network + nt.Render(); + + CHECK_OPENGL_ERROR + + +// glLoadName((int) sk.numL()); ///Load all the names + glLoadName(nt.sizeE()); + +// sk.RenderLine(cL); ///Render the current line. + nt.RenderLine(cL); + +// glPopName(); + glFlush(); ///Flush the buffer + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + CHECK_OPENGL_ERROR + glPopMatrix(); ///clear the vis matrices and pop the matrix + + // glEnable(GL_CULL_FACE); + hits = glRenderMode(GL_RENDER); ///Check for hits. + int found_hits = processHits(hits, selectBuf); ///Process the hits. + #ifdef TIMING + hit_time += gpuStopTimer(); + #endif + + return found_hits; ///return whether we hit something or not. + } + + //Given a size of the array (hits) and the memory holding it (buffer) + //returns whether a hit tool place or not. + int + processHits(GLint hits, GLuint buffer[]) + { + GLuint *ptr; ///pointer to the detection buffer + ptr = (GLuint *) buffer; + ptr++; + ptr++; //Skip the minimum depth value. + ptr++; //Skip the maximum depth value. + + + if(hits == 0) + { + return -1; + } + else + { +// printf ("%u ", *ptr); + return *ptr; + } + } + + void + clearCurrent() + { + cL.clear(); + cM.clear(); + } + + + void + addToNetwork(std::vector > L, std::vector M, stim::vec3 spos, stim::vec3 sdir, float smag) + { + //if the fiber is longer than 2 steps (the number it takes to diverge) + if(L.size() > 3) + { + //if we did not hit a fiber + if(last_fiber == -1) + { + spos[0] = spos[0]-sdir[0]*smag; + spos[1] = spos[1]-sdir[1]*smag; + spos[2] = spos[2]-sdir[2]*smag; + int h = selectObject(spos, -sdir, smag); + //did we start with a fiber? + if(h != -1 && h < nt.sizeE()) + nt.addEdge(L, M, h, -1); + else + nt.addEdge(L, M, -1, -1); + } + //if we hit a fiber? + else if(last_fiber != -1) + { + nt.addEdge(L, M, -1, last_fiber); + spos[0] = spos[0]-sdir[0]*smag; + spos[1] = spos[1]-sdir[1]*smag; + spos[2] = spos[2]-sdir[2]*smag; + int h = selectObject(spos, -sdir, smag); + //did start with a fiber? + if(h != -1 && h < nt.sizeE()){ + // std::cout << "got here double" << smag.str() << std::endl; + nt.addEdge(L, M, h, last_fiber); + } + else + { + nt.addEdge(L, M, -1, -1); + } + } + } + + #ifdef DEBUG + iter++; + #endif + } +/* + void + addToNetwork(std::vector > L, std::vector M) + { + if(L.size() > 3) + { + sk.Begin(stim::OBJ_LINE); + for(int i = 0; i < L.size(); i++) + { + sk.TexCoord(M[i]); + sk.Vertex(L[i][0], L[i][1], L[i][2]); + } + sk.End(); + + nt.addEdge(L,M); + #ifdef DEBUG + iter++; + #endif + } + } +*/ + + void + printSizes() + { + std::cout << nt.sizeE() << " edges " << std::endl; + std::cout << nt.sizeV() << " nodes " << std::endl; + } + + void + traceLine(stim::vec3 pos, float mag, int min_cost) + { + //starting (seed) position and magnitude. + last_fiber = -1; + cL.clear(); + cM.clear(); + cD.clear(); + + stim::vec3 spos = getPosition(); + stim::vec3 sdir = getDirection(); + float smag = getMagnitude(); + + setPosition(pos); + setMagnitude(mag); + + cL.push_back(p); + cD.push_back(d); + cM.push_back(m); +// stim::vec3 spos = getPosition(); +// float smag = getMagnitude(); +// stim::vec3 sdir = getDirection(); + +// Bind(); +// sk.Begin(stim::OBJ_LINE); + + + //sk.createFromSelf(GL_SELECT); + nt.createFromSelf(GL_SELECT); + int h; + bool started = false; + bool running = true; + stim::vec3 size(S[0]*R[0], S[1]*R[1], S[2]*R[2]); + while(running) + { + int cost = Step(); + if (cost > min_cost){ + running = false; + branchDetection2(); + addToNetwork(cL, cM, spos, sdir, smag); + #ifdef DEBUG + std::cerr << "the cost of " << cost << " > " << min_cost << std::endl; + #endif + break; + } else { + //Have we found the edge of the map? + pos = getPosition(); + if(p[0] > size[0] || p[1] > size[1] + || p[2] > size[2] || p[0] < 0 + || p[1] < 0 || p[2] < 0) + { + running = false; + branchDetection2(); + // addToNetwork(cL, cM); + addToNetwork(cL, cM, spos, sdir, smag); + #ifdef DEBUG + std::cerr << "I hit and edge" << std::endl; + #endif + break; + } + //If this is the first step in the trace, + // save the direction + //(to be used later to trace the fiber in the opposite direction) + if(started == false){ + rev = -getDirection(); + started = true; + } + //Has the template size gotten unreasonable? + mag = getMagnitude(); + if(m > 75 || m < 1){ + running = false; + branchDetection2(); + // addToNetwork(cL, cM); + addToNetwork(cL, cM, spos, sdir, smag); + #ifdef DEBUG + std::cerr << "The templates are too big" << std::endl; + #endif + break; + } + else + { + h = selectObject(p, getDirection(), m); + //Have we hit something previously traced? + if(h != -1){ + #ifdef DEBUG + std::cerr << "I hit the fiber " << h << std::endl; + #endif + last_fiber = h; + running = false; + branchDetection2(); + // addToNetwork(cL, cM); + addToNetwork(cL, cM, spos, sdir, smag); + break; + } + else { + cL.push_back(p); + cD.push_back(d); + cM.push_back(m); +// Unbind(); + CHECK_OPENGL_ERROR + } + } + } + } + #ifdef DEBUG + std::cout << "I broke out!" << std::endl; + #endif + } +}; +} +#endif diff --git a/tira/gl/gl_texture.h b/tira/gl/gl_texture.h new file mode 100644 index 0000000..38847c5 --- /dev/null +++ b/tira/gl/gl_texture.h @@ -0,0 +1,314 @@ +#ifndef STIM_GL_TEXTURE_H +#define STIM_GL_TEXTURE_H + +#include +#include +#include +#include "../grids/image_stack.h" +//Visual Studio requires GLEW +#ifdef _WIN32 + #include +#endif +//#include +#include +namespace stim{ + +/* +class gl_texture + Uses image_stack class in order to create a texture object. +*/ + +template +class gl_texture : public virtual image_stack +{ + protected: + //std::string path; + GLuint texID; //OpenGL object + GLenum texture_type; //1D, 2D, 3D + GLint interpolation; + GLint wrap; + GLenum cpu_type; + GLenum gpu_type; + GLenum format; //format for the texture (GL_RGBA, GL_LUMINANCE, etc.) + using image_stack::R; + //using image_stack::S; + using image_stack::ptr; + + /// Sets the internal texture_type, based on the data dimensions + void setTextureType(){ + if (R[3] > 1) //if the third dimension is greater than 1 + texture_type = GL_TEXTURE_3D; //this is a 3D texture + else if (R[2] > 1) //if the second dimension is greater than 1 + texture_type = GL_TEXTURE_2D; //this is a 2D texture + else if (R[1] > 1) //if the dimension value is greater than 1 + texture_type = GL_TEXTURE_1D; //this is a 1D texture + } + + //initializes important variables + void init() { + texID = 0; //initialize texture ID to zero, default if OpenGL returns an error + //memset(R, 0, sizeof(size_t)); + //memset(grid::S, 0, sizeof(F)); + } + + //guesses the color format of the texture + GLenum guess_format(){ + size_t channels = R[0]; + switch(channels){ + case 1: + return GL_LUMINANCE; + case 2: + return GL_RG; + case 3: + return GL_RGB; + case 4: + return GL_RGBA; + default: + std::cout<<"Error in stim::gl_texture - unable to guess texture format based on number of channels" << std::endl; + exit(1); + } + } + + //guesses the OpenGL CPU data type based on T + GLenum guess_cpu_type(){ + // The following is C++ 11 code, but causes problems on some compilers (ex. nvcc). Below is my best approximation to a solution + + //if(std::is_same::value) return CV_MAKETYPE(CV_8U, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_8S, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_16U, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_16S, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_32S, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_32F, (int)C()); + //if(std::is_same::value) return CV_MAKETYPE(CV_64F, (int)C()); + + if(typeid(T) == typeid(unsigned char)) return GL_UNSIGNED_BYTE; + if(typeid(T) == typeid(char)) return GL_BYTE; + if(typeid(T) == typeid(unsigned short)) return GL_UNSIGNED_SHORT; + if(typeid(T) == typeid(short)) return GL_SHORT; + if(typeid(T) == typeid(unsigned int)) return GL_UNSIGNED_INT; + if(typeid(T) == typeid(int)) return GL_INT; + if(typeid(T) == typeid(float)) return GL_FLOAT; + + std::cout<<"ERROR in stim::gl_texture - no valid data type found"<() { + init(); //initialize the texture with NULL values + interpolation = interp; //store the interpolation type + wrap = twrap; //store the wrap type + } + + ///@param is a mask indicating the files to load + ///Creates an instance of the gl_texture object and initializes it with a file list + + gl_texture(std::string file_mask, GLint interp = GL_LINEAR, GLint twrap = GL_REPEAT){ + init(); + interpolation = interp; //store interpolation type + wrap = twrap; //store wrap type + load_images(file_mask); + } + + ///Creates an instance of gl_texture and initializes with a file list + ///@param file_list is a list of files + ///@param interp is the type of texture interpolation (GL_LINEAR, GL_NEAREST) + ///@param twrap is the type of texture wrapping + gl_texture(std::vector file_list, GLint interp = GL_LINEAR, GLint twrap = GL_REPEAT){ + init(); + interpolation = interp; + wrap = twrap; + load_images(file_list); + } + + ///Attaches the texture to the current OpenGL context and makes it ready to render + void attach(){ + if(texID == 0) generate_texture(); //generate the texture if it doesn't already exist + else{ + std::cout<<"Texture has already been attached to a context."< getSize(){ + stim::vec size(R[1], R[2], R[3]); + return size; + } + + void getSize(size_t& x, size_t& y, size_t& z) { + x = R[0]; y = R[1]; z = R[2]; + } + + ///@param x size of the voxel in x direction + ///@param y size of the voxel in y direction + ///@param z size of the voxel in z direction + /// Sets the dimenstions of the voxels. + void setSpacing(float sx, float sy, float sz){ + grid::S[1] = sx; + grid::S[2] = sy; + grid::S[3] = sz; + } + + ///Returns a stim::vec that contains the x, y, z sizes of the voxel. + vec getDims(){ + vec dims(grid::S[1], grid::S[2], grid::S[3]); + return dims; + } + + /// Loads a series of files specified by a list of strings + /// @param file_list is the vector of file names as strings + void load_images(std::vector file_list){ + image_stack::load_images(file_list); //load the images + guess_parameters(); + } + + ///@param file_mask specifies the file(s) to be loaded + /// Sets the path and calls the loader on that path. + void load_images(std::string file_mask){ + image_stack::load_images(file_mask); //load images + guess_parameters(); + } + + /// Returns the GLuint id of the texture created by/associated with the + /// instance of the gl_texture class. + GLuint getTexture(){ + return texID; + } + + + T* getData(){ + return ptr; + } + + void setData(T* rts) + { + + } + + }; + +} + + + + + + +#endif diff --git a/tira/gl/rtsSourceCode.h b/tira/gl/rtsSourceCode.h new file mode 100644 index 0000000..9e53e45 --- /dev/null +++ b/tira/gl/rtsSourceCode.h @@ -0,0 +1,65 @@ +#ifndef RTSSOURCECODE_H +#define RTSSOURCECODE_H + +#include +#include +#include +#include + +using namespace std; + +///This class defines generic source code that can be loaded from text files. It is primarily used by the rts_glShaderProgram class for GLSL programming. + +class rtsSourceCode +{ +public: + vector source; //the actual source code + void clear() /// +//#include "windows.h" +#include +#include "rtsSourceCode.h" + +class rts_glShaderObject +{ +private: + void init() + { + id = 0; + compiled = false; + type = GL_FRAGMENT_SHADER; + } +public: + bool compiled; + GLenum type; + rtsSourceCode source; + GLuint id; + string log; + + rts_glShaderObject(GLenum type, const char* filename) + { + init(); //initialize the shader + SetType(type); //set the shader type + LoadSource(filename); //load the source code + } + rts_glShaderObject(GLenum type, rtsSourceCode sourceCode) + { + init(); //initialize the shader + SetType(type); //set the shader type + source = sourceCode; + } + rts_glShaderObject() + { + init(); + } + rts_glShaderObject(GLenum type) + { + init(); + SetType(type); + } + void LoadSource(const char* filename) + { + source = rtsSourceCode(filename); //get the shader source code + + } + void SetType(GLenum type) + { + if(id != 0) //if a shader currently exists, delete it + { + glDeleteShader(id); + id = 0; + } + type = type; + id = glCreateShader(type); //create a shader object + if(id == 0) //if a shader was not created, log an error + { + log = "Error getting shader ID from OpenGL"; + return; + } + } + void UploadSource() + { + //create the structure for the shader source code + GLsizei count = source.source.size(); + GLchar** code_string = new GLchar*[count]; + GLint* length = new GLint[count]; + for(int l = 0; l + +using namespace std; + +class rts_glShaderProgram +{ +private: + void get_uniforms() + { + GLint num_uniforms; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &num_uniforms); //get the number of uniform variables + GLint max_name_length; + glGetProgramiv(id, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_length); //get the maximum uniform name length + GLchar* name_buffer = new GLchar[max_name_length]; //create a buffer to store the name + GLsizei length; //I'm not using these yet + GLint size; + GLenum type; //variable's data type + GLint location; //GPU location of the variable + for(int i=0; i shader_list; //list of opengl shaders + vector uniform_list; //list of active uniform variables + vector texture_list; //list of texture maps + + rts_glShaderProgram() + { + linked = false; + id = 0; + } + void AttachShader(rts_glShaderObject shader) + { + if(id == 0) + { + Init(); + } + if(shader.id == 0) //if the shader is invalid + { + log = "Shader is invalid"; + return; + } + + //attach the shader to the program + glAttachShader(id, shader.id); //attach the shader to the program in OpenGL + CHECK_OPENGL_ERROR + shader_list.push_back(shader); //push the shader onto our list for later access + } + //type = GL_FRAGMENT_SHADER or GL_VERTEX_SHADER + void AttachShader(GLenum type, const char* filename) + { + rts_glShaderObject shader(type, filename); + AttachShader(shader); + } + void AttachShader(GLenum type, rtsSourceCode source) + { + rts_glShaderObject shader(type, source); + AttachShader(shader); + } + void PrintLog() + { + cout<::iterator iter; + for(iter = shader_list.begin(); iter != shader_list.end(); iter++) + { + (*iter).Compile(); + //(*iter).PrintLog(); + } + } + void Link() + { + glLinkProgram(id); //link the current shader program + GLint link_status; //test to see if the link went alright + glGetProgramiv(id, GL_LINK_STATUS, &link_status); + if(link_status != GL_TRUE) + { + linked = false; + } + else + linked = true; + + GLsizei length; + GLchar buffer[1000]; + glGetProgramInfoLog(id, 1000, &length, buffer); + log = buffer; + + get_uniforms(); //create the list of active uniform variables + } + void BeginProgram() + { + CHECK_OPENGL_ERROR + if(id == 0) //if the program is invalid, return + { + log = "Invalid program, cannot use."; + return; + } + if(!linked) + { + cout<<"Shader Program used without being linked."< 0) + glActiveTexture(GL_TEXTURE0); + CHECK_OPENGL_ERROR + + //return to OpenGL default shading + glUseProgram(0); + CHECK_OPENGL_ERROR + } + void PrintUniforms() + { + cout<<"Shader Uniforms: "< +#include + +using namespace std; + +enum rtsUniformEnum {RTS_FLOAT, RTS_INT, RTS_BOOL, RTS_FLOAT_MATRIX}; + +///This class stores a single uniform variable for GLSL and is designed to be used by the rts_glShaderProgram class. +struct rts_glShaderUniform +{ +public: + string name; //the name of the variable + GLint location; //the location in the program + void* p_value; //pointer to the global data representing the value in main memory + GLenum type; //variable type (float, int, vec2, etc.) + //rtsUniformEnum rts_type; //type of variable in rts format + //unsigned int num; //the number of values required by the variable (1 for float, 2 for vec2, etc.) + string log; + + //void convert_type(GLenum gl_type); //converts the OpenGL data type to something useful for rts + void submit_to_gpu() + { + if(location < 0) + return; + if(p_value == NULL) + { + cout<<"Error in uniform address: "< +#include "rts/math/vector.h" +#include "rts/gl/error.h" +#include + +namespace stim{ + +///This class stores an OpenGL texture map and is used by rts_glShaderProgram. +class glTexture +{ +private: + void get_type() //guesses the texture type based on the size + { + if(size[1] == 0) + texture_type = GL_TEXTURE_1D; + else if(size[2] == 0) + texture_type = GL_TEXTURE_2D; + else + texture_type = GL_TEXTURE_3D; + } + void set_wrapping() //set the texture wrapping based on the dimensions + { + CHECK_OPENGL_ERROR + switch(texture_type) + { + case GL_TEXTURE_3D: + glTexParameteri(texture_type, GL_TEXTURE_WRAP_R_EXT, GL_REPEAT); + case GL_TEXTURE_2D: + glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); + case GL_TEXTURE_1D: + glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_REPEAT); + break; + case GL_TEXTURE_RECTANGLE_ARB: + glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + break; + + default: + break; + } + CHECK_OPENGL_ERROR + } + //void set_bits(GLvoid* bits); +public: + vector size; //vector representing the size of the texture + GLuint name; //texture name assigned by OpenGL + GLenum texture_type; //1D, 2D, 3D + GLint internal_format; //number of components (ex. 4 for RGBA) + GLenum pixel_format; //type of data (RGBA, LUMINANCE) + GLenum data_type; //data type of the bits (float, int, etc.) + + //constructor + glTexture() + { + name = 0; + } + glTexture(GLvoid *bits, + GLenum type = GL_TEXTURE_2D, + GLsizei width = 256, + GLsizei height = 256, + GLsizei depth = 0, + GLint internalformat = 1, + GLenum format = GL_LUMINANCE, + GLenum datatype = GL_UNSIGNED_BYTE, + GLint interpolation = GL_LINEAR) + { + init(bits, type, width, height, depth, internalformat, format, datatype, interpolation); + } + + void begin() + { + glEnable(texture_type); + CHECK_OPENGL_ERROR + glBindTexture(texture_type, name); + CHECK_OPENGL_ERROR + } + void end() + { + glDisable(texture_type); + CHECK_OPENGL_ERROR + } + + ///Creates an OpenGL texture map. This function requires basic information about the texture map as well as a pointer to the bit data describing the texture. + void init(GLvoid *bits, + GLenum type = GL_TEXTURE_2D, + GLsizei width = 256, + GLsizei height = 256, + GLsizei depth = 0, + GLint internalformat = 1, + GLenum format = GL_LUMINANCE, + GLenum datatype = GL_UNSIGNED_BYTE, + GLint interpolation = GL_LINEAR) + { + CHECK_OPENGL_ERROR + if(name != 0) + glDeleteTextures(1, &name); + + + CHECK_OPENGL_ERROR + if(datatype == GL_FLOAT) + { + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //I honestly don't know what this does but it fixes problems + } + else if(datatype == GL_UNSIGNED_BYTE) + { + //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + //glPixelStorei(GL_PACK_ALIGNMENT, 1); + } + else if(datatype == GL_UNSIGNED_SHORT) + { + //glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + //glPixelStorei(GL_PACK_ALIGNMENT, 2); + } + CHECK_OPENGL_ERROR + glGenTextures(1, &name); //get the texture name from OpenGL + //cout<<"OpenGL Name: "<(width, height, depth); //assign the texture size + //get_type(); //guess the type based on the size + texture_type = type; //set the type of texture + glEnable(texture_type); //enable the texture map + CHECK_OPENGL_ERROR + glBindTexture(texture_type, name); //bind the texture for editing + CHECK_OPENGL_ERROR + set_wrapping(); //set the texture wrapping parameters + CHECK_OPENGL_ERROR + glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, interpolation); //set filtering + CHECK_OPENGL_ERROR + glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, interpolation); + CHECK_OPENGL_ERROR + internal_format = internalformat; //set the number of components per pixel + pixel_format = format; //set the pixel format + data_type = datatype; //set the data type + SetBits(bits); //send the bits to the OpenGL driver + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //replace the specified vertex color + CHECK_OPENGL_ERROR + glDisable(texture_type); + + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + void Clean() + { + if(name != 0) + { + glDeleteTextures(1, &name); + CHECK_OPENGL_ERROR + name = 0; + } + } + void SetBits(GLvoid *bits) + { + glEnable(texture_type); //enable the texture map + CHECK_OPENGL_ERROR + glBindTexture(texture_type, name); + CHECK_OPENGL_ERROR + + switch(texture_type) + { + case GL_TEXTURE_3D: + glTexImage3D(texture_type, 0, internal_format, size[0], size[1], size[2], 0, pixel_format, data_type, bits); + CHECK_OPENGL_ERROR + break; + case GL_TEXTURE_2D: + case GL_TEXTURE_RECTANGLE_ARB: + glTexImage2D(texture_type, 0, internal_format, size[0], size[1], 0, pixel_format, data_type, bits); + CHECK_OPENGL_ERROR + break; + case GL_TEXTURE_1D: + glTexImage1D(texture_type, 0, internal_format, size[0], 0, pixel_format, data_type, bits); + CHECK_OPENGL_ERROR + break; + default: + //glTexImage2D(texture_type, 0, internal_format, size.x, size.y, 0, pixel_format, data_type, bits); + break; + } + CHECK_OPENGL_ERROR + } + void ResetBits(GLvoid *bits) + { + glEnable(texture_type); //enable the texture map + CHECK_OPENGL_ERROR + glBindTexture(texture_type, name); + CHECK_OPENGL_ERROR + + switch(texture_type) + { + case GL_TEXTURE_3D: + //glTexImage3D(texture_type, 0, internal_format, size.x, size.y, size.z, 0, pixel_format, data_type, bits); + break; + case GL_TEXTURE_2D: + case GL_TEXTURE_RECTANGLE_ARB: + glTexSubImage2D(texture_type, 0, 0, 0, size[0], size[1], pixel_format, data_type, bits); + CHECK_OPENGL_ERROR + break; + case GL_TEXTURE_1D: + //glTexImage1D(texture_type, 0, internal_format, size.x, 0, pixel_format, data_type, bits); + break; + default: + //glTexImage2D(texture_type, 0, internal_format, size.x, size.y, 0, pixel_format, data_type, bits); + break; + } + glDisable(texture_type); + CHECK_OPENGL_ERROR + } + void* GetBits(GLenum format, GLenum type) + { + //returns the texture data + + int components; + switch(format) + { + case GL_RED: + case GL_GREEN: + case GL_BLUE: + case GL_ALPHA: + case GL_LUMINANCE: + components = 1; + break; + case GL_LUMINANCE_ALPHA: + components = 2; + break; + case GL_RGB: + case GL_BGR: + components = 3; + break; + case GL_RGBA: + case GL_BGRA: + components = 4; + break; + } + + int type_size; + switch(type) + { + case GL_UNSIGNED_BYTE: + case GL_BYTE: + type_size = sizeof(char); + break; + case GL_UNSIGNED_SHORT: + case GL_SHORT: + type_size = sizeof(short); + break; + case GL_UNSIGNED_INT: + case GL_INT: + type_size = sizeof(int); + break; + case GL_FLOAT: + type_size = sizeof(float); + break; + } + + //allocate memory for the texture + void* result = malloc(components*type_size * size[0] * size[1]); + + begin(); + glGetTexImage(texture_type, 0, format, type, result); + + CHECK_OPENGL_ERROR + end(); + + + return result; + + } +}; + +} + +#define RTS_UNKNOWN 0 + +#endif diff --git a/tira/grids/grid.h b/tira/grids/grid.h new file mode 100644 index 0000000..0de4def --- /dev/null +++ b/tira/grids/grid.h @@ -0,0 +1,223 @@ +#ifndef STIM_GRID_H +#define STIM_GRID_H + +#include +#include +#include +#include +#include + +#include + +namespace stim{ + +/**This object describes a generic D-dimensional grid containing data of type T. + Functions are provided for saving and loading binary data. + +**/ +template +class grid{ + +protected: + + size_t R[D]; //elements in each dimension + F S[D]; //spacing between element samples + T* ptr; //pointer to the data (on the GPU or CPU) + + + + ///Initializes a grid by allocating the necessary memory and setting all values to zero + void init(){ + ptr = NULL; //initialize the data pointer to NULL + memset(R, 0, sizeof(size_t) * D); //set the resolution to zero + for(size_t d = 0; d < D; d++) S[d] = (F)1.0; //initialize the spacing to unity + } + + void alloc(){ + if(ptr != NULL) free(ptr); //if memory has already been allocated, free it + size_t N = samples(); //calculate the total number of values + ptr = (T*)calloc(sizeof(T), N); //allocate memory to store the grid + } + +public: + + ///Default constructor doesn't do anything + grid(){ + init(); + } + + ///Constructor used to specify the grid size as a vector + + /// @param _R is a vector describing the grid resolution + grid( stim::vec _R){ + for (size_t d = 0; d < D; d++) + R[d] = _R[d]; + init(); + } + + ///Return the total number of values in the binary file + size_t samples(){ + size_t s = 1; + for(size_t d = 0; d < D; d++) + s *= R[d]; + return s; + } + + ///Return the number of bytes in the binary file + size_t bytes(){ + return samples() * sizeof(T); + } + + void + setDim(stim::vec s){ + for(size_t d = 0; d < D; d++) + S[d] = s[d]; + } + + ///Constructor used to specify the grid size as a set of parameters + /// @param X0... is a list of values describing the grid size along each dimension + /*grid( size_t X0, ...){ + R[0] = X0; //set the grid size of the first dimension + va_list ap; //get a variable list + va_start(ap, X0); //start the variable list at the first element + for(size_t d = 1; d X, unsigned long header = 0){ + for(size_t d = 0; d < D; d++) + R[d] = X[d]; //set the sample resolution + init(); //allocate space for the data + std::fstream file; + file.open(filename.c_str(), std::ios::in | std::ios::binary); //open the file as binary for writing + file.seekg(header, std::ios::beg); //seek past the header + file.read((char *)ptr, samples() * sizeof(T)); //read the data + } + + ///Gets a single value from the grid given a set of coordinates + /// @param x0... is a list of coordinates specifying the desired value + /*T get(unsigned long x0, ...){ + + va_list ap; //create a variable list + + unsigned long F = 1; //initialize the dimension size to 1 + unsigned long idx = x0; + + va_start(ap, x0); //start a variable list + for(unsigned int d = 1; d +class grid : public stim::grid{ + +public: + + /// Convert grid coordinates (integers) into world coordinates (F) based on the pixel spacing + void grid2volume(size_t xi, size_t yi, size_t zi, F& x, F& y, F&z){ + + } + + /// Use linear interpolation to get a value from the grid at (x, y, z) in VOLUME space (based on voxel size) + T lerp(F x, F y, F z){ + + } + /// Create a resampled grid with isotropic voxel sizes + grid3 resample_iso(){ + + //find the smallest spacing + //create a new grid of the appropriate size + //use linear interpolation to resample the old grid into the new grid + } + + +}; +} //end namespace stim + +#endif \ No newline at end of file diff --git a/tira/grids/image_stack.h b/tira/grids/image_stack.h new file mode 100644 index 0000000..536434d --- /dev/null +++ b/tira/grids/image_stack.h @@ -0,0 +1,225 @@ +#ifndef STIM_IMAGE_STACK_H +#define STIM_IMAGE_STACK_H + +#include +#include +#include +#include +#include + +namespace stim{ + +///This class is used to load 3D grid data from stacks of images +// The class uses a 4D grid object, where the first dimension is a color value. +template +class image_stack : public virtual stim::grid{ + + enum image_type {stimAuto, stimMono, stimRGB, stimRGBA}; + +protected: + //using stim::grid::S; + using stim::grid::R; + using stim::grid::ptr; + using stim::grid::S; + +public: + //default constructor + image_stack() : grid() { + + } + + /// Overloads grid::samples() to return the number of samples associated with a given spatial dimension + /// this is necessary because R[0] stores the color + size_t samples(size_t d){ + return grid::samples(d + 1); + } + + size_t samples(){ + return R[1] * R[2] * R[3]; //return the number of spatial samples + } + + // get the number of pixels in each dimension + size_t nc() { + return R[0]; + } + size_t nx() { + return R[1]; + } + size_t ny() { + return R[2]; + } + size_t nz() { + return R[3]; + } + + /// Returns the number of color channels + size_t channels(){ + return R[0]; + } + + /// Overloads grid::size() to return the size of the grid associated with a given spatial dimension + F size(size_t d){ + return grid::size(d + 1); + } + + /// Sets the spacing between samples in the image stack + void spacing(F sx, F sy, F sz){ + grid::S[1] = sx; //set the sample spacing for the appropriate spatial dimension + grid::S[2] = sy; + grid::S[3] = sz; + } + + F spacing(size_t d){ + return grid::spacing(d + 1); + } + + /// Overloads the spacing parameter to set the size of the grid associated with a given spatial dimension + //void spacing(F sx, F sy = 1.0f, F sz = 1.0f){ + // grid::spacing((F)1.0, sx, sy, sz); + //} + + /// Load all of the images specified by a list of strings + /// @param string_list is a list of file names specifying images + void load_images(std::vector string_list){ + + //if there are no matching files, exit + if(string_list.size() == 0){ + std::cout<<"STIM ERROR (image_stack): No matching files for loading a stack."< I(string_list[0]); //load the first image and set all of the image_stack proparties + + R[0] = I.channels(); //set the number of color channels + R[1] = I.width(); //set the stack height and width based on the image size + R[2] = I.height(); + R[3] = string_list.size(); //set the stack z-resolution based on the number of images + + ptr = (T*)malloc(grid::bytes()); //allocate storage space + + //load and copy each image into the grid + for(unsigned int i = 0; i I(string_list[i]); //load the image + I.get_interleaved_rgb(&ptr[ i * R[0] * R[1] * R[2] ]); //retrieve the interlaced data from the image - store it in the grid + } + } + + /// Load a stack of images based on a file mask. Images are loaded in alphanumeric order + /// @param file_mask is the mask describing the images to be loaded + void load_images(std::string file_mask){ + stim::filename file_path(file_mask); //get the path for the images + std::vector file_list = file_path.get_list(); //get the list of files + std::vector string_list(file_list.size()); //allocate space for an array of strings + for(size_t f = 0; f < file_list.size(); f++){ //for each file name in the list + string_list[f] = file_list[f].str(); //convert the file name to a string + } + load_images(string_list); //load all of the images in the list + } + + ///Inserts image I into slot i. + /// @param stim::image I; image to insert. + /// @int I, where to place the image. + void insert_image(stim::image I, int i){ + I.get_interleaved_rgb(&ptr[i *R[0] *R[1] *R[2] ]); + } + + ///Saves a single page to an image file + /// @param file_name is the name of the image file to be created + /// @param i is the page to be saved + void save_image(std::string file_name, unsigned int i){ + stim::image I; //create an image + I.set_interleaved(&ptr[ i * R[0] * R[1] * R[2] ], R[1], R[2], R[0]); //retrieve the interlaced data from the image - store it in the grid +// I.set_interleaved_rgb(&ptr[ i * R[0] * R[1] * R[2] ], R[1], R[2], R[0]); //retrieve the interlaced data from the image - store it in the grid + I.save(file_name); + } + + ///Sets the dimensions of the image in each direction + ///Voxel-size. + /// @param x size in the x direction + /// @param y size in the y direction + /// @param z size in the z direction + void + set_dim(float x, float y, float z) + { + grid::S[0] = 1; + grid::S[1] = x; + grid::S[2] = y; + grid::S[3] = z; + } + + ///set dimensions of the grid. + /// @param channels, number of channels in each image. + /// @param width, number of pixels in width each image. + /// @param height, number of pixels in height. + /// @param depth, number of pixels in depth. + void init(int channels, int width, int height, int depth) + { + //R.resize(4); + R[0] = channels; + R[1] = width; + R[2] = height; + R[3] = depth; + + ptr = (T*)malloc(sizeof(T) * samples()); + memset(ptr, 0, sizeof(T) * samples()); + } + + ///Saves the entire stack to a set of images + /// @param file_mask is the mask describing how the file names will be saved (ex. image????.bmp) + void save_images(std::string file_mask){ + + stim::filename file_path(file_mask); + //stim::filename abs_file_path = file_pat + + //create a list of file names + std::vector file_list = stim::wildcards::increment(file_path.str(), 0, R[3]-1, 1); + for (int i = 0; i < R[3]; i++) { + save_image(file_list[i], i); + } + } + + /// Returns the pixel at the specified point + T get(unsigned int x, unsigned int y, unsigned int z, unsigned int c = 0){ + return ptr[z * R[0] * R[1] * R[2] + y * R[0] * R[1] + x * R[0] + c]; + } + + /// Returns the world-space position at an index point (i, j, k) + vec3 p(size_t i, size_t j, size_t k){ + vec3 result; + result[0] = (F)i * S[1]; + result[1] = (F)j * S[2]; + result[2] = (F)k * S[3]; + return result; + } + + // set the pixel at the specified point + void set(size_t i, size_t j, size_t k, T value, size_t c = 0){ + ptr[k * R[0] * R[1] * R[2] + j * R[0] * R[1] + i * R[0] + c] = value; + } + void set(T* Ptr, size_t k) { + + for (unsigned i = 0; i < R[0] * R[1] * R[2]; i++) + ptr[i + k * R[0] * R[1] * R[2]] = Ptr[i]; + } + void copy(T* Ptr) { + ptr = Ptr; + } + + + /* This was causing compiler errors. I don't think this function call exists anywhere: + + void read(std::string file, unsigned int X, unsigned int Y, unsigned int Z, unsigned int C = 1, unsigned int header = 0){ + read(file, stim::vec(C, X, Y, Z), header); + } + */ + + T* data(){ + return ptr; + } + +}; + + +} + +#endif diff --git a/tira/iVote/ivote2.cuh b/tira/iVote/ivote2.cuh new file mode 100644 index 0000000..f10fec3 --- /dev/null +++ b/tira/iVote/ivote2.cuh @@ -0,0 +1,168 @@ +#ifndef STIM_IVOTE2_CUH +#define STIM_IVOTE2_CUH + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace stim { + // this function precomputes the atan2 values + template + void atan_2(T* cpuTable, unsigned int rmax) { + int xsize = 2 * rmax + 1; //initialize the width and height of the window which atan2 are computed in. + int ysize = 2 * rmax + 1; + int yi = rmax; // assign the center coordinates of the atan2 window to yi and xi + int xi = rmax; + for (int xt = 0; xt < xsize; xt++) { //for each element in the atan2 table + for (int yt = 0; yt < ysize; yt++) { + int id = yt * xsize + xt; //convert the current 2D coordinates to 1D + int xd = xi - xt; // calculate the distance between the pixel and the center of the atan2 window + int yd = yi - yt; + T atan_2d = atan2((T)yd, (T)xd); // calculate the angle between the pixel and the center of the atan2 window and store the result. + cpuTable[id] = atan_2d; + } + } + } + + //this kernel invert the 2D image + template + __global__ void cuda_invert(T* gpuI, size_t x, size_t y) { + // calculate the 2D coordinates for this current thread. + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + + if (xi >= x || yi >= y) return; + size_t i = yi * x + xi; // convert 2D coordinates to 1D + gpuI[i] = 255 - gpuI[i]; //invert the pixel intensity + } + + + + //this function calculate the threshold using OTSU method + template + T th_otsu(T* pts, size_t pixels, unsigned int th_num = 20) { + T Imax = pts[0]; //initialize the maximum value to the first one + T Imin = pts[0]; //initialize the maximum value to the first on + + for (size_t n = 0; n < pixels; n++) { //for every value + if (pts[n] > Imax) { //if the value is higher than the current max + Imax = pts[n]; + } + } + for (size_t n = 0; n< pixels; n++) { //for every value + if (pts[n] < Imin) { //if the value is higher than the current max + Imin = pts[n]; + } + } + + T th_step = ((Imax - Imin) / th_num); + std::vector var_b; + for (unsigned int t0 = 0; t0 < th_num; t0++) { + T th = t0 * th_step + Imin; + unsigned int n_b(0), n_o(0); //these variables save the number of elements that are below and over the threshold + T m_b(0), m_o(0); //these variables save the mean value for each cluster + for (unsigned int idx = 0; idx < pixels; idx++) { + if (pts[idx] <= th) { + m_b += pts[idx]; + n_b += 1; + } + else { + m_o += pts[idx]; + n_o += 1; + } + } + + m_b = m_b / n_b; //calculate the mean value for the below threshold cluster + m_o = m_o / n_o; //calculate the mean value for the over threshold cluster + + var_b.push_back(n_b * n_o * pow((m_b - m_o), 2)); + } + + std::vector::iterator max_var = std::max_element(var_b.begin(), var_b.end()); //finding maximum elements in the vector + size_t th_idx = std::distance(var_b.begin(), max_var); + T threshold = Imin + (T)(th_idx * th_step); + return threshold; + } + + //this function performs the 2D iterative voting algorithm on the image stored in the gpu + template + void gpu_ivote2(T* gpuI, unsigned int rmax, size_t x, size_t y, bool invert = false, T t = 0, int iter = 8, T phi = 15.0f * (float)stim::PI / 180, int conn = 8, bool debug = false) { + + size_t pixels = x * y; //compute the size of input image + // + if (invert) { //if inversion is required call the kernel to invert the image + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads((unsigned int)sqrt(max_threads), (unsigned int)sqrt(max_threads)); + dim3 blocks((unsigned int)x / threads.x + 1, (unsigned int)y / threads.y + 1); + cuda_invert << > > (gpuI, x, y); + } + // + size_t table_bytes = (size_t)(pow(2 * rmax + 1, 2) * sizeof(T)); // create the atan2 table + T* cpuTable = (T*)malloc(table_bytes); //assign memory on the cpu for atan2 table + atan_2(cpuTable, rmax); //call the function to precompute the atan2 table + T* gpuTable; HANDLE_ERROR(cudaMalloc(&gpuTable, table_bytes)); + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, table_bytes, cudaMemcpyHostToDevice)); //copy atan2 table to the gpu + + size_t bytes = pixels* sizeof(T); //calculate the bytes of the input + float dphi = phi / iter; //change in phi for each iteration + + float* gpuGrad; HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes * 2)); //allocate space to store the 2D gradient + float* gpuVote; HANDLE_ERROR(cudaMalloc(&gpuVote, bytes)); //allocate space to store the vote image + + stim::cuda::gpu_gradient_2d(gpuGrad, gpuI, x, y); //calculate the 2D gradient + stim::cuda::gpu_cart2polar(gpuGrad, x, y); //convert cartesian coordinate of gradient to the polar + + for (int i = 0; i < iter; i++) { //for each iteration + cudaMemset(gpuVote, 0, bytes); //reset the vote image to 0 + stim::cuda::gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x, y, debug); //perform voting + stim::cuda::gpu_update_dir(gpuVote, gpuGrad, gpuTable, phi, rmax, x, y, debug); //update the voter directions + phi = phi - dphi; //decrement phi + } + stim::cuda::gpu_local_max(gpuI, gpuVote, conn, x, y); //calculate the local maxima + + if (t > 0) { + T* pts = (T*)malloc(bytes); //allocate memory on the cpu to store the output of iterative voting + HANDLE_ERROR(cudaMemcpy(pts, gpuI, bytes, cudaMemcpyDeviceToHost)); //copy the output from gpu to the cpu memory + + T threshold; + threshold = t; + + size_t ind; + for (size_t ix = 0; ix < x; ix++) { + for (size_t iy = 0; iy < y; iy++) { + ind = iy * x + ix; + if (pts[ind] > threshold) { + pts[ind] = 1; + } + else pts[ind] = 0; + } + } + HANDLE_ERROR(cudaMemcpy(gpuI, pts, bytes, cudaMemcpyHostToDevice)); //copy the points to the gpu + } + + } + + + template + void cpu_ivote2(T* cpuI, unsigned int rmax, size_t x, size_t y, float &gpu_time, bool invert = false, T t = 0, int iter = 8, T phi = 15.0f * (float)stim::PI / 180, int conn = 8, bool debug = false) { + size_t bytes = x*y * sizeof(T); + T* gpuI; //allocate space on the gpu to save the input image + + gpuTimer_start(); + HANDLE_ERROR(cudaMalloc(&gpuI, bytes)); + HANDLE_ERROR(cudaMemcpy(gpuI, cpuI, bytes, cudaMemcpyHostToDevice)); //copy the image to the gpu + stim::gpu_ivote2(gpuI, rmax, x, y, invert, t, iter, phi, conn, debug); //call the gpu version of the ivote + HANDLE_ERROR(cudaMemcpy(cpuI, gpuI, bytes, cudaMemcpyDeviceToHost)); //copy the output to the cpu + + gpu_time = gpuTimer_end(); + } +} +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/iter_vote2.cuh b/tira/iVote/ivote2/iter_vote2.cuh new file mode 100644 index 0000000..55f3445 --- /dev/null +++ b/tira/iVote/ivote2/iter_vote2.cuh @@ -0,0 +1,17 @@ +#ifndef STIM_CUDA_ITER_VOTE2_H +#define STIM_CUDA_ITER_VOTE2_H + +//extern bool DEBUG; + +#include "update_dir_bb.cuh" +#include "vote_atomic_bb.cuh" + +namespace stim{ + namespace cuda{ + + } +} + + + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/local_max.cuh b/tira/iVote/ivote2/local_max.cuh new file mode 100644 index 0000000..eebc64c --- /dev/null +++ b/tira/iVote/ivote2/local_max.cuh @@ -0,0 +1,95 @@ +#ifndef STIM_CUDA_LOCAL_MAX_H +#define STIM_CUDA_LOCAL_MAX_H + +# include +# include +#include + +namespace stim{ + namespace cuda{ + + // this kernel calculates the local maximum for finding the cell centers + template + __global__ void cuda_local_max(T* gpuCenters, T* gpuVote, int conn, int x, int y){ + + // calculate the 2D coordinates for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + if(xi >= x || yi >= y) + return; + + // convert 2D coordinates to 1D + int i = yi * x + xi; + + gpuCenters[i] = 0; //initialize the value at this location to zero + + T val = gpuVote[i]; + + for(unsigned int xl = xi - conn; xl < xi + conn; xl++){ + for(unsigned int yl = yi - conn; yl < yi + conn; yl++){ + if(xl >= 0 && xl < x && yl >= 0 && yl < y){ + unsigned int il = yl * x + xl; + if(gpuVote[il] > val){ + return; + } + if (gpuVote[il] == val){ + + if( il > i){ + return; + } + } + } + } + } + + gpuCenters[i] = gpuVote[i]; + } + + template + void gpu_local_max(T* gpuCenters, T* gpuVote, unsigned int conn, unsigned int x, unsigned int y){ + + unsigned int max_threads = stim::maxThreadsPerBlock(); + /*dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y);*/ + dim3 threads((unsigned int)sqrt(max_threads), (unsigned int)sqrt(max_threads) ); + dim3 blocks((unsigned int)x/threads.x + 1, (unsigned int)y/threads.y + 1); + + //call the kernel to find the local maximum. + cuda_local_max <<< blocks, threads >>>(gpuCenters, gpuVote, conn, x, y); + } + + template + void cpu_local_max(T* cpuCenters, T* cpuVote, unsigned int conn, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + // allocate space on the GPU for the detected cell centes + T* gpuCenters; + cudaMalloc(&gpuCenters, bytes); + + //allocate space on the GPU for the input Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //copy the Vote image data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuVote, cpuVote, bytes, cudaMemcpyHostToDevice)); + + //call the GPU version of the local max function + gpu_local_max(gpuCenters, gpuVote, conn, x, y); + + //copy the cell centers data to the CPU + cudaMemcpy(cpuCenters, gpuCenters, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuCenters); + cudaFree(gpuVote); + } + + } +} + + + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/update_dir.cuh b/tira/iVote/ivote2/update_dir.cuh new file mode 100644 index 0000000..3052bf4 --- /dev/null +++ b/tira/iVote/ivote2/update_dir.cuh @@ -0,0 +1,217 @@ +#ifndef STIM_CUDA_UPDATE_DIR_H +#define STIM_CUDA_UPDATE_DIR_H + +# include +# include +#include +#include + +namespace stim{ + namespace cuda{ + + // this kernel calculates the voting direction for the next iteration based on the angle between the location of this voter and the maximum vote value in its voting area. + template + __global__ void cuda_update_dir(T* gpuDir, cudaTextureObject_t in, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ float s_vote[]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + + // calculate the 2D coordinates for this current thread. + int xi = bxi + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // calculate the voting direction based on the grtadient direction + float theta = gpuGrad[2*i]; + + //initialize the vote direction to zero + gpuDir[i] = 0; + + // define a local variable to maximum value of the vote image in the voting area for this voter + float max = 0; + + // define two local variables for the x and y coordinations where the maximum happened + int id_x = 0; + int id_y = 0; + + //calculate the width of the shared memory block + int swidth = 2 * rmax + blockDim.x; + + // compute the size of window which will be checked for finding the voting area for this voter + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + int bxs = bxi - rmax; + + for(int yr = -rmax; yr <= rmax; yr++){ + + //copy the portion of the image necessary for this block to shared memory + __syncthreads(); + stim::cuda::sharedMemcpy_tex2D(s_vote, in, bxs, yi + yr , swidth, 1, threadIdx, blockDim); + __syncthreads(); + + //if the current thread is outside of the image, it doesn't have to be computed + if(xi < x && yi < y){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + unsigned int ind_t = (rmax - yr) * x_table + rmax - xr; + + // calculate the angle between the voter and the current pixel in x and y directions + float atan_angle = gpuTable[ind_t]; + + // calculate the voting direction based on the grtadient direction + int idx_share_update = xr + tx_rmax ; + float share_vote = s_vote[idx_share_update]; + + // check if the current pixel is located in the voting area of this voter. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) max) { + + max = share_vote; + id_x = xr; + id_y = yr; + } + } + } + } + } + + unsigned int ind_m = (rmax - id_y) * x_table + (rmax - id_x); + float new_angle = gpuTable[ind_m]; + + if(xi < x && yi < y) + gpuDir[i] = new_angle; + + } + + // this kernel updates the gradient direction by the calculated voting direction. + template + __global__ void cuda_update_grad(T* gpuGrad, T* gpuDir, int x, int y){ + + // calculate the 2D coordinates for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + // convert 2D coordinates to 1D + int i = yi * x + xi; + + //update the gradient image with the vote direction + gpuGrad[2*i] = gpuDir[i]; + } + + template + void gpu_update_dir(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = sizeof(T) * pixels; + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y); + + //define a channel descriptor for a single 32-bit channel + cudaChannelFormatDesc channelDesc = + cudaCreateChannelDesc(32, 0, 0, 0, + cudaChannelFormatKindFloat); + cudaArray* cuArray; //declare the cuda array + cudaMallocArray(&cuArray, &channelDesc, x, y); //allocate the cuda array + + // Copy the image data from global memory to the array + cudaMemcpyToArray(cuArray, 0, 0, gpuVote, bytes, + cudaMemcpyDeviceToDevice); + + // Specify texture + struct cudaResourceDesc resDesc; //create a resource descriptor + memset(&resDesc, 0, sizeof(resDesc)); //set all values to zero + resDesc.resType = cudaResourceTypeArray; //specify the resource descriptor type + resDesc.res.array.array = cuArray; //add a pointer to the cuda array + + // Specify texture object parameters + struct cudaTextureDesc texDesc; //create a texture descriptor + memset(&texDesc, 0, sizeof(texDesc)); //set all values in the texture descriptor to zero + texDesc.addressMode[0] = cudaAddressModeWrap; //use wrapping (around the edges) + texDesc.addressMode[1] = cudaAddressModeWrap; + texDesc.filterMode = cudaFilterModePoint; //use linear filtering + texDesc.readMode = cudaReadModeElementType; //reads data based on the element type (32-bit floats) + texDesc.normalizedCoords = 0; //not using normalized coordinates + + // Create texture object + cudaTextureObject_t texObj = 0; + cudaCreateTextureObject(&texObj, &resDesc, &texDesc, NULL); + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*(1)*4; + + // allocate space on the GPU for the updated vote direction + T* gpuDir; + cudaMalloc(&gpuDir, bytes); + + //call the kernel to calculate the new voting direction + cuda_update_dir <<< blocks, threads, share_bytes >>>(gpuDir, texObj, gpuGrad, gpuTable, phi, rmax, x , y); + + //call the kernel to update the gradient direction + cuda_update_grad <<< blocks, threads >>>(gpuGrad, gpuDir, x , y); + + //free allocated memory + cudaFree(gpuDir); + + cudaDestroyTextureObject(texObj); + cudaFreeArray(cuArray); + + } + + template + void cpu_update_dir(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //copy the input vote image to the GPU + HANDLE_ERROR(cudaMemcpy(gpuVote, cpuVote, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the update direction function + gpu_update_dir(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the new gradient image back to the CPU + cudaMemcpy(cpuGrad, gpuGrad, bytes*2, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif diff --git a/tira/iVote/ivote2/update_dir_bb.cuh b/tira/iVote/ivote2/update_dir_bb.cuh new file mode 100644 index 0000000..b9da2be --- /dev/null +++ b/tira/iVote/ivote2/update_dir_bb.cuh @@ -0,0 +1,181 @@ +#ifndef STIM_CUDA_UPDATE_DIR_BB_H +#define STIM_CUDA_UPDATE_DIR_BB_H + +# include +# include +#include +#include +#include +#include +#include + +//#define RMAX_TEST 8 + +namespace stim{ + namespace cuda{ + + template + __global__ void cuda_update_dir(T* gpuDir, T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + extern __shared__ T S[]; + T* shared_atan = S; + size_t n_table = (rmax * 2 + 1) * (rmax * 2 + 1); + stim::cuda::threadedMemcpy((char*)shared_atan, (char*)gpuTable, sizeof(T) * n_table, threadIdx.x, blockDim.x); + + //T* shared_vote = &S[n_table]; + //size_t template_size_x = (blockDim.x + 2 * rmax); + //size_t template_size_y = (blockDim.y + 2 * rmax); + //stim::cuda::threadedMemcpy2D((char*)shared_vote, (char*)gpuVote, template_size_x, template_size_y, x, threadIdx.y * blockDim.x + threadIdx.x, blockDim.x * blockDim.y); + + int xi = blockIdx.x * blockDim.x + threadIdx.x; //calculate the 2D coordinates for this current thread. + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + if(xi >= x || yi >= y) return; //if the index is outside of the image, terminate the kernel + + int i = yi * x + xi; //convert 2D coordinates to 1D + float theta = gpuGrad[2*i]; //calculate the voting direction based on the grtadient direction - global memory fetch + + stim::aabb2 bb(xi, yi); //initialize a bounding box at the current point + bb.insert(xi + ceil(rmax * cos(theta)), ceil(yi + rmax * sin(theta))); + bb.insert(xi + ceil(rmax * cos(theta - phi)), yi + ceil(rmax * sin(theta - phi))); //insert one corner of the triangle into the bounding box + bb.insert(xi + ceil(rmax * cos(theta + phi)), yi + ceil(rmax * sin(theta + phi))); //insert the final corner into the bounding box + + int x_table = 2*rmax +1; + T rmax_sq = rmax * rmax; + + int lut_i; + T dx_sq, dy_sq; + + bb.trim_low(0, 0); //make sure the bounding box doesn't go outside the image + bb.trim_high(x-1, y-1); + + int by, bx; + int dx, dy; //coordinate relative to (xi, yi) + + T v; + T max_v = 0; //initialize the maximum vote value to zero + T alpha; + int max_dx = bb.low[0] - xi; + int max_dy = bb.low[1] - yi; + for(by = bb.low[1]; by <= bb.high[1]; by++){ //for each element in the bounding box + dy = by - yi; //calculate the y coordinate of the current point relative to yi + dy_sq = dy * dy; + for(bx = bb.low[0]; bx <= bb.high[0]; bx++){ + dx = bx - xi; + dx_sq = dx * dx; + lut_i = (rmax - dy) * x_table + rmax - dx; + alpha = shared_atan[lut_i]; + if(dx_sq + dy_sq < rmax_sq && abs(alpha - theta) < phi){ + v = gpuVote[by * x + bx]; // find the vote value for the current counter + if(v > max_v){ + max_v = v; + max_dx = dx; + max_dy = dy; + } + } + } + } + gpuDir[i] = atan2((T)max_dy, (T)max_dx); + } + + + + // this kernel updates the gradient direction by the calculated voting direction. + template + __global__ void cuda_update_grad(T* gpuGrad, T* gpuDir, size_t x, size_t y){ + + // calculate the 2D coordinates for this current thread. + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + + if(xi >= x || yi >= y) return; + + // convert 2D coordinates to 1D + size_t i = yi * x + xi; + + //update the gradient image with the vote direction + gpuGrad[2*i] = gpuDir[i]; + } + + template + void gpu_update_dir(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, size_t x, size_t y, bool DEBUG = false){ + + //calculate the number of bytes in the array + size_t bytes = x * y * sizeof(T); + + // allocate space on the GPU for the updated vote direction + T* gpuDir; + HANDLE_ERROR( cudaMalloc(&gpuDir, bytes) ); + + unsigned int max_threads = stim::maxThreadsPerBlock(); + + dim3 threads( (unsigned int)sqrt(max_threads), (unsigned int)sqrt(max_threads) ); + dim3 blocks((unsigned int)x/threads.x + 1, (unsigned int)y/threads.y + 1); + + size_t table_bytes = sizeof(T) * (rmax * 2 + 1) * (rmax * 2 + 1); + //size_t curtain = 2 * rmax; + //size_t template_bytes = sizeof(T) * (threads.x + curtain) * (threads.y + curtain); + size_t shared_mem_req = table_bytes;// + template_bytes; + if (DEBUG) std::cout << "Shared Memory required: " << shared_mem_req << std::endl; + + size_t shared_mem = stim::sharedMemPerBlock(); + if(shared_mem_req > shared_mem){ + std::cout<<"Error: insufficient shared memory for this implementation of cuda_update_dir()."<>>(gpuDir, gpuVote, gpuGrad, gpuTable, phi, rmax, (int)x , (int)y); + + //call the kernel to update the gradient direction + cuda_update_grad <<< blocks, threads >>>(gpuGrad, gpuDir, (int)x , (int)y); + //free allocated memory + HANDLE_ERROR( cudaFree(gpuDir) ); + + } + + template + void cpu_update_dir(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //copy the input vote image to the GPU + HANDLE_ERROR(cudaMemcpy(gpuVote, cpuVote, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the update direction function + gpu_update_dir(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the new gradient image back to the CPU + cudaMemcpy(cpuGrad, gpuGrad, bytes*2, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/update_dir_shared.cuh b/tira/iVote/ivote2/update_dir_shared.cuh new file mode 100644 index 0000000..91aa717 --- /dev/null +++ b/tira/iVote/ivote2/update_dir_shared.cuh @@ -0,0 +1,184 @@ +#ifndef STIM_CUDA_UPDATE_DIR_SHARED_H +#define STIM_CUDA_UPDATE_DIR_SHARED_H + +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the voting direction for the next iteration based on the angle between the location of this voter and the maximum vote value in its voting area. + template + __global__ void cuda_update_dir(T* gpuDir, T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ float s_vote[]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + + // calculate the 2D coordinates for this current thread. + int xi = bxi + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // calculate the voting direction based on the grtadient direction + float theta = gpuGrad[2*i]; + + //initialize the vote direction to zero + gpuDir[i] = 0; + + // define a local variable to maximum value of the vote image in the voting area for this voter + float max = 0; + + // define two local variables for the x and y coordinations where the maximum happened + int id_x = 0; + int id_y = 0; + + //calculate the width of the shared memory block + int swidth = 2 * rmax + blockDim.x; + + // compute the size of window which will be checked for finding the voting area for this voter + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + int bxs = bxi - rmax; + + for(int yr = -rmax; yr <= rmax; yr++){ + //if (yi+yr >= 0 && yi + yr < y){ + //copy the portion of the image necessary for this block to shared memory + __syncthreads(); + cpyG2S1D(s_vote, gpuVote, bxs, yi + yr , swidth, 1, threadIdx, blockDim, x, y); + __syncthreads(); + + //if the current thread is outside of the image, it doesn't have to be computed + if(xi < x && yi < y){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + unsigned int ind_t = (rmax - yr) * x_table + rmax - xr; + + // calculate the angle between the voter and the current pixel in x and y directions + float atan_angle = gpuTable[ind_t]; + + // calculate the voting direction based on the grtadient direction + int idx_share_update = xr + tx_rmax ; + float share_vote = s_vote[idx_share_update]; + + // check if the current pixel is located in the voting area of this voter. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) max) { + + max = share_vote; + id_x = xr; + id_y = yr; + } + } + } + } + //} + } + + unsigned int ind_m = (rmax - id_y) * x_table + (rmax - id_x); + float new_angle = gpuTable[ind_m]; + + if(xi < x && yi < y) + gpuDir[i] = new_angle; + + } + + // this kernel updates the gradient direction by the calculated voting direction. + template + __global__ void cuda_update_grad(T* gpuGrad, T* gpuDir, int x, int y){ + + // calculate the 2D coordinates for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + // convert 2D coordinates to 1D + int i = yi * x + xi; + + //update the gradient image with the vote direction + gpuGrad[2*i] = gpuDir[i]; + } + + template + void gpu_update_dir(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y); + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*(1)*4; + + // allocate space on the GPU for the updated vote direction + T* gpuDir; + cudaMalloc(&gpuDir, bytes); + + //call the kernel to calculate the new voting direction + cuda_update_dir <<< blocks, threads, share_bytes >>>(gpuDir, gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //call the kernel to update the gradient direction + cuda_update_grad <<< blocks, threads >>>(gpuGrad, gpuDir, x , y); + + //free allocated memory + cudaFree(gpuDir); + + } + + template + void cpu_update_dir(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //copy the input vote image to the GPU + HANDLE_ERROR(cudaMemcpy(gpuVote, cpuVote, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the update direction function + gpu_update_dir(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the new gradient image back to the CPU + cudaMemcpy(cpuGrad, gpuGrad, bytes*2, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/update_dir_threshold_global.cuh b/tira/iVote/ivote2/update_dir_threshold_global.cuh new file mode 100644 index 0000000..f73efa6 --- /dev/null +++ b/tira/iVote/ivote2/update_dir_threshold_global.cuh @@ -0,0 +1,159 @@ +#ifndef STIM_CUDA_UPDATE_DIR_THRESHOLD_GLOBALD_H +#define STIM_CUDA_UPDATE_DIR_THRESHOLD_GLOBAL_H + +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the voting direction for the next iteration based on the angle between the location of this voter and the maximum vote value in its voting area. + template + __global__ void cuda_update_dir(T* gpuDir, T* gpuVote, T* gpuTh, T* gpuTable, T phi, int rmax, int th_size, int x, int y){ + + + + // calculate the coordinate for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + // calculate the voting direction based on the grtadient direction + float theta = gpuTh[3*xi]; + + //calculate the position and x, y coordinations of this voter in the original image + unsigned int i_v = gpuTh[3*xi+2]; + unsigned int y_v = i_v/x; + unsigned int x_v = i_v - (y_v*x); + + //initialize the vote direction to zero + gpuDir[xi] = 0; + + // define a local variable to maximum value of the vote image in the voting area for this voter + float max = 0; + + // define two local variables for the x and y coordinations where the maximum happened + int id_x = 0; + int id_y = 0; + + // compute the size of window which will be checked for finding the voting area for this voter + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + if(xi < th_size){ + + for(int yr = -rmax; yr <= rmax; yr++){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + unsigned int ind_t = (rmax - yr) * x_table + rmax - xr; + + // find the angle between the voter and the current pixel in x and y directions + float atan_angle = gpuTable[ind_t]; + + // check if the current pixel is located in the voting area of this voter. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) max) { + + max = vote_c; + id_x = xr; + id_y = yr; + } + } + } + } + + + unsigned int ind_m = (rmax - id_y) * x_table + (rmax - id_x); + float new_angle = gpuTable[ind_m]; + gpuDir[xi] = new_angle; + } + + } + + // this kernel updates the gradient direction by the calculated voting direction. + template + __global__ void cuda_update_grad(T* gpuTh, T* gpuDir, int th_size, int x, int y){ + + // calculate the coordinate for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + + + //update the gradient image with the vote direction + gpuTh[3*xi] = gpuDir[xi]; + } + + template + void gpu_update_dir(T* gpuVote, T* gpuTh, T* gpuTable, T phi, unsigned int rmax, unsigned int th_size, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes_th = th_size* sizeof(T); + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads); + dim3 blocks(th_size/threads.x+1); + + // allocate space on the GPU for the updated vote direction + T* gpuDir; + cudaMalloc(&gpuDir, bytes_th); + + //call the kernel to calculate the new voting direction + cuda_update_dir <<< blocks, threads>>>(gpuDir, gpuVote, gpuTh, gpuTable, phi, rmax, th_size, x , y); + + //call the kernel to update the gradient direction + cuda_update_grad <<< blocks, threads >>>(gpuTh, gpuDir, th_size, x , y); + + //free allocated memory + cudaFree(gpuDir); + + } + + template + void cpu_update_dir(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //copy the input vote image to the GPU + HANDLE_ERROR(cudaMemcpy(gpuVote, cpuVote, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the update direction function + gpu_update_dir(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the new gradient image back to the CPU + cudaMemcpy(cpuGrad, gpuGrad, bytes*2, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote.cuh b/tira/iVote/ivote2/vote.cuh new file mode 100644 index 0000000..94250eb --- /dev/null +++ b/tira/iVote/ivote2/vote.cuh @@ -0,0 +1,175 @@ +#ifndef STIM_CUDA_VOTE_H +#define STIM_CUDA_VOTE_H + +# include +# include +#include +#include + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, cudaTextureObject_t in, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ float2 s_grad[]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + + // calculate the 2D coordinates for this current thread. + int xi = bxi + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // define a local variable to sum the votes from the voters + float sum = 0; + + //calculate the width of the shared memory block + int swidth = 2 * rmax + blockDim.x; + + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + int bxs = bxi - rmax; + + //for every line (along y) + for(int yr = -rmax; yr <= rmax; yr++){ + + //copy the portion of the image necessary for this block to shared memory + __syncthreads(); + stim::cuda::sharedMemcpy_tex2D(s_grad, in, bxs, yi + yr , swidth, 1, threadIdx, blockDim); + __syncthreads(); + + if(xi < x && yi < y){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + //find the location of this voter in the atan2 table + int id_t = (yr + rmax) * x_table + xr + rmax; + + // calculate the angle between the pixel and the current voter in x and y directions + float atan_angle = gpuTable[id_t]; + + // calculate the voting direction based on the grtadient direction + int idx_share = xr + tx_rmax ; + float2 g = s_grad[idx_share]; + float theta = g.x; + + // check if the current voter is located in the voting area of this pixel. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = sizeof(T) * pixels; + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y); + + // Allocate CUDA array in device memory + + //define a channel descriptor for a single 32-bit channel + cudaChannelFormatDesc channelDesc = + cudaCreateChannelDesc(32, 32, 0, 0, + cudaChannelFormatKindFloat); + cudaArray* cuArray; //declare the cuda array + cudaMallocArray(&cuArray, &channelDesc, x, y); //allocate the cuda array + + // Copy the image data from global memory to the array + cudaMemcpyToArray(cuArray, 0, 0, gpuGrad, bytes*2, + cudaMemcpyDeviceToDevice); + + // Specify texture + struct cudaResourceDesc resDesc; //create a resource descriptor + memset(&resDesc, 0, sizeof(resDesc)); //set all values to zero + resDesc.resType = cudaResourceTypeArray; //specify the resource descriptor type + resDesc.res.array.array = cuArray; //add a pointer to the cuda array + + // Specify texture object parameters + struct cudaTextureDesc texDesc; //create a texture descriptor + memset(&texDesc, 0, sizeof(texDesc)); //set all values in the texture descriptor to zero + texDesc.addressMode[0] = cudaAddressModeWrap; //use wrapping (around the edges) + texDesc.addressMode[1] = cudaAddressModeWrap; + texDesc.filterMode = cudaFilterModePoint; //use linear filtering + texDesc.readMode = cudaReadModeElementType; //reads data based on the element type (32-bit floats) + texDesc.normalizedCoords = 0; //not using normalized coordinates + + // Create texture object + cudaTextureObject_t texObj = 0; + cudaCreateTextureObject(&texObj, &resDesc, &texDesc, NULL); + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*(1)*2*4; + + //call the kernel to do the voting + + cuda_vote <<< blocks, threads,share_bytes >>>(gpuVote, texObj, gpuTable, phi, rmax, x , y); + + cudaDestroyTextureObject(texObj); + cudaFreeArray(cuArray); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif diff --git a/tira/iVote/ivote2/vote_atomic.cuh b/tira/iVote/ivote2/vote_atomic.cuh new file mode 100644 index 0000000..fc0ce47 --- /dev/null +++ b/tira/iVote/ivote2/vote_atomic.cuh @@ -0,0 +1,116 @@ +#ifndef STIM_CUDA_VOTE_ATOMIC_H +#define STIM_CUDA_VOTE_ATOMIC_H + +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + + // calculate the 2D coordinates for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // calculate the voting direction based on the grtadient direction + float theta = gpuGrad[2*i]; + //calculate the amount of vote for the voter + float mag = gpuGrad[2*i + 1]; + + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + if(xi < x && yi < y){ + //for every line (along y) + for(int yr = -rmax; yr <= rmax; yr++){ + for(int xr = -rmax; xr <= rmax; xr++){ + if ((yi+yr)>=0 && (yi+yr)=0 && (xi+xr) + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y); + + // specify share memory + //unsigned int share_bytes = (2*rmax + threads.x)*(1)*2*4; + + //call the kernel to do the voting + cuda_vote <<< blocks, threads>>>(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote_atomic_bb.cuh b/tira/iVote/ivote2/vote_atomic_bb.cuh new file mode 100644 index 0000000..dd033a6 --- /dev/null +++ b/tira/iVote/ivote2/vote_atomic_bb.cuh @@ -0,0 +1,151 @@ +#ifndef STIM_CUDA_VOTE_ATOMIC_BB_H +#define STIM_CUDA_VOTE_ATOMIC_BB_H + +# include +# include +#include +#include +#include +#include +#include + + + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, size_t x, size_t y, bool gradmag = true){ + + extern __shared__ T S[]; + T* shared_atan = S; + size_t n_table = (rmax * 2 + 1) * (rmax * 2 + 1); + stim::cuda::threadedMemcpy((char*)shared_atan, (char*)gpuTable, sizeof(T) * n_table, threadIdx.x, blockDim.x); + + // calculate the 2D coordinates for this current thread. + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + + if(xi >= x || yi >= y) return; + // convert 2D coordinates to 1D + size_t i = yi * x + xi; + + // calculate the voting direction based on the grtadient direction + float theta = gpuGrad[2*i]; + //calculate the amount of vote for the voter + float mag = gpuGrad[2*i + 1]; + + + stim::aabb2 bb(xi, yi); //initialize a bounding box at the current point + bb.insert(xi + ceil(rmax * cos(theta)), ceil(yi + rmax * sin(theta))); + bb.insert(xi + ceil(rmax * cos(theta - phi)), yi + ceil(rmax * sin(theta - phi))); //insert one corner of the triangle into the bounding box + bb.insert(xi + ceil(rmax * cos(theta + phi)), yi + ceil(rmax * sin(theta + phi))); //insert the final corner into the bounding box + + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + + int lut_i; + T dx_sq, dy_sq; + + bb.trim_low(0, 0); //make sure the bounding box doesn't go outside the image + bb.trim_high(x-1, y-1); + + size_t by, bx; + int dx, dy; + + unsigned int ind_g; //initialize the maximum vote value to zero + T alpha; + + for(by = bb.low[1]; by <= bb.high[1]; by++){ //for each element in the bounding box + dy = by - yi; //calculate the y coordinate of the current point relative to yi + dy_sq = dy * dy; + for(bx = bb.low[0]; bx <= bb.high[0]; bx++){ + dx = bx - xi; + dx_sq = dx * dx; + lut_i = (rmax - dy) * x_table + rmax - dx; + alpha = shared_atan[lut_i]; + if(dx_sq + dy_sq < rmax_sq && abs(alpha - theta) < phi){ + ind_g = (by)*x + (bx); + if(gradmag) atomicAdd(&gpuVote[ind_g], mag); //add the gradient magnitude (if the gradmag flag is enabled) + else atomicAdd(&gpuVote[ind_g], 1.0f); //otherwise just add 1 + + } + } + } + + } + + + /// Iterative voting for an image + /// @param gpuVote is the resulting vote image + /// @param gpuGrad is the gradient of the input image + /// @param gpuTable is the pre-computed atan2() table + /// @param phi is the angle of the vote region + /// @param rmax is the estimated radius of the blob (defines the "width" of the vote region) + /// @param x and y are the spatial dimensions of the gradient image + /// @param gradmag defines whether or not the gradient magnitude is taken into account during the vote + template + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, size_t x, size_t y, bool DEBUG = false, bool gradmag = true){ + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads( (unsigned int)sqrt(max_threads), (unsigned int)sqrt(max_threads) ); + dim3 blocks((unsigned int)x/threads.x + 1, (unsigned int)y/threads.y + 1); + size_t table_bytes = sizeof(T) * (rmax * 2 + 1) * (rmax * 2 + 1); + size_t shared_mem_req = table_bytes;// + template_bytes; + if (DEBUG) std::cout<<"Shared Memory required: "< shared_mem){ + std::cout<<"Error: insufficient shared memory for this implementation of cuda_vote()."<>>(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y, gradmag); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote_atomic_shared.cuh b/tira/iVote/ivote2/vote_atomic_shared.cuh new file mode 100644 index 0000000..102bd20 --- /dev/null +++ b/tira/iVote/ivote2/vote_atomic_shared.cuh @@ -0,0 +1,166 @@ +#ifndef STIM_CUDA_VOTE_ATOMIC_SHARED_H +#define STIM_CUDA_VOTE_ATOMIC_SHARED_H + +# include +# include +#include +#include + +//#include "writebackshared.cuh" +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to the shared memory + extern __shared__ float s_vote[]; + // calculate the 2D coordinates for this current thread. + int bxi = blockIdx.x * blockDim.x; + int byi = blockIdx.y * blockDim.y; + int xi = bxi + threadIdx.x; + int yi = byi + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // calculate the voting direction based on the gradient direction + float theta = gpuGrad[2*i]; + //calculate the amount of vote for the voter + float mag = gpuGrad[2*i + 1]; + + //find the starting points and size of window, wich will be copied to the shared memory + int bxs = bxi - rmax; + int bys = byi - rmax; + int xwidth = 2*rmax + blockDim.x; + int ywidth = 2*rmax + blockDim.y; + //compute the coordinations of this pixel in the 2D-shared memory. + int sx_rx = threadIdx.x + rmax; + int sy_ry = threadIdx.y + rmax; + // compute the size of window which will be checked for finding the counters for this voter + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + //calculate some parameters for indexing shared memory + //calculate the total number of threads available + unsigned int tThreads = blockDim.x * blockDim.y; + //calculate the current 1D thread ID + unsigned int ti = threadIdx.y * (blockDim.x) + threadIdx.x; + //calculate the number of iteration required + unsigned int In = xwidth*ywidth/tThreads + 1; + if(xi < x && yi < y){ + __syncthreads(); + //initialize the shared memory to zero + for (unsigned int i = 0; i < In; i++){ + unsigned int sIdx0 = i * tThreads + ti; + if (sIdx0< xwidth*ywidth) { + s_vote[sIdx0] = 0; + } + } + __syncthreads(); + //for every line (along y) + for(int yr = -rmax; yr <= rmax; yr++){ + //compute the position of the current voter in the shared memory along the y axis. + unsigned int sIdx_y1d = (sy_ry + yr)* xwidth; + for(int xr = -rmax; xr <= rmax; xr++){ + + //find the location of the current pixel in the atan2 table + unsigned int ind_t = (rmax - yr) * x_table + rmax - xr; + + // calculate the angle between the voter and the current pixel in x and y directions + float atan_angle = gpuTable[ind_t]; + + // check if the current pixel is located in the voting area of this voter. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) = xwidth*ywidth) return; + + unsigned int sy = sIdx/xwidth; + unsigned int sx = sIdx - (sy * xwidth); + + unsigned int gx = bxs + sx; + unsigned int gy = bys + sy; + if (gx + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(sqrt(max_threads), sqrt(max_threads)); + dim3 blocks(x/threads.x + 1 , y/threads.y+1); + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*(2*rmax + threads.y)*sizeof(T); + + //call the kernel to do the voting + cuda_vote <<< blocks, threads, share_bytes>>>(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote_shared.cuh b/tira/iVote/ivote2/vote_shared.cuh new file mode 100644 index 0000000..f53fe5d --- /dev/null +++ b/tira/iVote/ivote2/vote_shared.cuh @@ -0,0 +1,139 @@ +#ifndef STIM_CUDA_VOTE_SHARED_H +#define STIM_CUDA_VOTE_SHARED +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ float s_grad[]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + + // calculate the 2D coordinates for this current thread. + int xi = bxi + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // define a local variable to sum the votes from the voters + float sum = 0; + + //calculate the width of the shared memory block + int swidth = 2 * rmax + blockDim.x; + + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + int bxs = bxi - rmax; + + //for every line (along y) + for(int yr = -rmax; yr <= rmax; yr++){ + if (yi+yr=0){ + //copy the portion of the image necessary for this block to shared memory + __syncthreads(); + cpyG2S1D2ch(s_grad, gpuGrad, bxs, yi + yr , 2*swidth, 1, threadIdx, blockDim, x, y); + __syncthreads(); + + if(xi < x && yi < y){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + //find the location of this voter in the atan2 table + int id_t = (yr + rmax) * x_table + xr + rmax; + + // calculate the angle between the pixel and the current voter in x and y directions + float atan_angle = gpuTable[id_t]; + + // calculate the voting direction based on the grtadient direction + int idx_share = xr + tx_rmax ; + float theta = s_grad[idx_share*2]; + float mag = s_grad[idx_share*2 + 1]; + + + // check if the current voter is located in the voting area of this pixel. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + (x %threads.x == 0 ? 0:1) , y); + + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*1*2*sizeof(T); + + //call the kernel to do the voting + cuda_vote <<< blocks, threads,share_bytes >>>(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote_shared_32-32.cuh b/tira/iVote/ivote2/vote_shared_32-32.cuh new file mode 100644 index 0000000..23c9481 --- /dev/null +++ b/tira/iVote/ivote2/vote_shared_32-32.cuh @@ -0,0 +1,150 @@ +#ifndef STIM_CUDA_VOTE_SHARED_H +#define STIM_CUDA_VOTE_SHARED +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, int rmax, int x, int y){ + + //generate a pointer to shared memory (size will be specified as a kernel parameter) + extern __shared__ float s_grad[]; + + //calculate the start point for this block + int bxi = blockIdx.x * blockDim.x; + int byi = blockIdx.y * blockDim.y; + // calculate the 2D coordinates for this current thread. + int xi = bxi + threadIdx.x; + int yi = byi + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + // define a local variable to sum the votes from the voters + float sum = 0; + + //calculate the width of the shared memory block + int xwidth = 2 * rmax + blockDim.x; + int ywidth = 2 * rmax + blockDim.y; + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + int tx_rmax = threadIdx.x + rmax; + int bxs = bxi - rmax; + int bys = byi - rmax; + //compute the coordinations of this pixel in the 2D-shared memory. + int sx_rx = threadIdx.x + rmax; + int sy_ry = threadIdx.y + rmax; + //copy the portion of the image necessary for this block to shared memory + __syncthreads(); + cpyG2S2D2ch(s_grad, gpuGrad, bxs, bys, 2*xwidth, ywidth, threadIdx, blockDim, x, y); + __syncthreads(); + + for(int yr = -rmax; yr <= rmax; yr++){ + int yi_v = (yi + yr) ; + //compute the position of the current voter in the shared memory along the y axis. + unsigned int sIdx_y1d = (sy_ry + yr)* xwidth; + //if (yi+yr=0){ + if(xi < x && yi < y){ + + for(int xr = -rmax; xr <= rmax; xr++){ + + //compute the position of the current voter in the 2D-shared memory along the x axis. + unsigned int sIdx_x = (sx_rx + xr); + //find the 1D index of this voter in the 2D-shared memory. + unsigned int s_Idx = (sIdx_y1d + sIdx_x); + unsigned int s_Idx2 = s_Idx * 2; + + //find the location of this voter in the atan2 table + int id_t = (yr + rmax) * x_table + xr + rmax; + + // calculate the angle between the pixel and the current voter in x and y directions + float atan_angle = gpuTable[id_t]; + + // calculate the voting direction based on the grtadient direction + //int idx_share = xr + tx_rmax ; + float theta = s_grad[s_Idx2]; + float mag = s_grad[s_Idx2 + 1]; + + + // check if the current voter is located in the voting area of this pixel. + if (((xr * xr + yr *yr)< rmax_sq) && (abs(atan_angle - theta) + void gpu_vote(T* gpuVote, T* gpuGrad, T* gpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(sqrt(max_threads), sqrt(max_threads)); + dim3 blocks(x/threads.x + 1 , y/threads.y+1); + + + // specify share memory + unsigned int share_bytes = (2*rmax + threads.x)*(2*rmax + threads.y)*2*sizeof(T); + + //call the kernel to do the voting + cuda_vote <<< blocks, threads,share_bytes >>>(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/iVote/ivote2/vote_threshold_global.cuh b/tira/iVote/ivote2/vote_threshold_global.cuh new file mode 100644 index 0000000..7a944f1 --- /dev/null +++ b/tira/iVote/ivote2/vote_threshold_global.cuh @@ -0,0 +1,112 @@ +#ifndef STIM_CUDA_VOTE_THRESHOLD_GLOBAL_H +#define STIM_CUDA_VOTE_THRESHOLD_GLOBAL_H +# include +# include +#include +#include +#include "cpyToshare.cuh" + +namespace stim{ + namespace cuda{ + + // this kernel calculates the vote value by adding up the gradient magnitudes of every voter that this pixel is located in their voting area + template + __global__ void cuda_vote(T* gpuVote, T* gpuTh, T* gpuTable, T phi, int rmax, int th_size, int x, int y){ + + + // calculate the x coordinate for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + + // calculate the voting direction based on the grtadient direction + float theta = gpuTh[3*xi]; + //find the gradient magnitude for the current voter + float mag = gpuTh[3*xi + 1]; + //calculate the position and x, y coordinations of this voter in the original image + unsigned int i_v = gpuTh[3*xi+2]; + unsigned int y_v = i_v/x; + unsigned int x_v = i_v - (y_v*x); + + // compute the size of window which will be checked for finding the proper voters for this pixel + int x_table = 2*rmax +1; + int rmax_sq = rmax * rmax; + if(xi < th_size){ + for(int yr = -rmax; yr <= rmax; yr++){ + for(int xr = -rmax; xr <= rmax; xr++){ + if ((y_v+yr)>=0 && (y_v+yr)=0 && (x_v+xr) + void gpu_vote(T* gpuVote, T* gpuTh, T* gpuTable, T phi, unsigned int rmax, unsigned int th_size, unsigned int x, unsigned int y){ + + + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads); + dim3 blocks(th_size/threads.x + 1); + + //call the kernel to do the voting + cuda_vote <<< blocks, threads>>>(gpuVote, gpuTh, gpuTable, phi, rmax, th_size, x , y); + + } + + + template + void cpu_vote(T* cpuVote, T* cpuGrad,T* cpuTable, T phi, unsigned int rmax, unsigned int x, unsigned int y){ + + //calculate the number of bytes in the array + unsigned int bytes = x * y * sizeof(T); + + //calculate the number of bytes in the atan2 table + unsigned int bytes_table = (2*rmax+1) * (2*rmax+1) * sizeof(T); + + //allocate space on the GPU for the Vote Image + T* gpuVote; + cudaMalloc(&gpuVote, bytes); + + //allocate space on the GPU for the input Gradient image + T* gpuGrad; + HANDLE_ERROR(cudaMalloc(&gpuGrad, bytes*2)); + + //copy the Gradient Magnitude data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuGrad, cpuGrad, bytes*2, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the atan2 table + T* gpuTable; + HANDLE_ERROR(cudaMalloc(&gpuTable, bytes_table)); + + //copy the atan2 values to the GPU + HANDLE_ERROR(cudaMemcpy(gpuTable, cpuTable, bytes_table, cudaMemcpyHostToDevice)); + + //call the GPU version of the vote calculation function + gpu_vote(gpuVote, gpuGrad, gpuTable, phi, rmax, x , y); + + //copy the Vote Data back to the CPU + cudaMemcpy(cpuVote, gpuVote, bytes, cudaMemcpyDeviceToHost) ; + + //free allocated memory + cudaFree(gpuTable); + cudaFree(gpuVote); + cudaFree(gpuGrad); + } + + } +} + +#endif \ No newline at end of file diff --git a/tira/image/CImg.h b/tira/image/CImg.h new file mode 100644 index 0000000..e768a5a --- /dev/null +++ b/tira/image/CImg.h @@ -0,0 +1,62402 @@ +/* + # + # 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 253 + +/*----------------------------------------------------------- + # + # 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 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 +#ifdef Status +#undef Status +#define _cimg_redefine_Status +#endif +#include +#include +#if CV_MAJOR_VERSION >=3 +#define _cimg_fourcc cv::VideoWriter::fourcc +#else +#define _cimg_fourcc CV_FOURCC +#endif +#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 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)(cimg::type::is_nan(x)?0:x<0?-1:x>0); + } + + //! Return the nearest power of 2 higher than given value. + template + inline cimg_ulong nearest_pow2(const T& x) { + cimg_ulong i = 1; + 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 Index to read from the pressed keys history (index \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 Index to read from the released keys history (index \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 index %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 index %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 index %u " + "for primitive [%u]", + nb_points,nb_primitives,w,c); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete color/texture data for primitive [%u], " + "%u values missing", + nb_points,nb_primitives,c,(unsigned int)(ptrs - ptre)); + 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 index %u " + "for primitive [%u]", + nb_points,nb_primitives,w,o); + return false; + } + } else ptrs+=w*h*s; + } + if (ptrs>ptre) { + if (error_message) cimg_sprintf(error_message, + "CImg3d (%u,%u) has incomplete opacity data for primitive [%u]", + nb_points,nb_primitives,o); + 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*)(cimg_ulong)-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*)(cimg_ulong)-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; + } else if (*s=='0' && (s[1]=='x' || s[1]=='X')) { // Hexadecimal number + is_sth = *ss=='-'; + if (cimg_sscanf(s + 2,"%x%c",&arg1,&sep)==1) { + nb = 1; + val = (double)arg1; + if (is_sth) val = -val; + } + } + if (!nb) nb = cimg_sscanf(ss,"%lf%c%c",&val,&(sep=0),&(end=0)); + if (nb==1) _cimg_mp_constant(val); + if (nb==2 && sep=='%') _cimg_mp_constant(val/100); + + if (ss1==se) switch (*ss) { // One-char reserved variable + case 'c' : _cimg_mp_return(reserved_label[(int)'c']!=~0U?reserved_label[(int)'c']:_cimg_mp_slot_c); + case 'd' : _cimg_mp_return(reserved_label[(int)'d']!=~0U?reserved_label[(int)'d']:20); + case 'e' : _cimg_mp_return(reserved_label[(int)'e']!=~0U?reserved_label[(int)'e']:27); + case 'h' : _cimg_mp_return(reserved_label[(int)'h']!=~0U?reserved_label[(int)'h']:19); + case 'l' : _cimg_mp_return(reserved_label[(int)'l']!=~0U?reserved_label[(int)'l']:26); + case 'r' : _cimg_mp_return(reserved_label[(int)'r']!=~0U?reserved_label[(int)'r']:22); + case 's' : _cimg_mp_return(reserved_label[(int)'s']!=~0U?reserved_label[(int)'s']:21); + case 't' : _cimg_mp_return(reserved_label[(int)'t']!=~0U?reserved_label[(int)'t']:17); + case 'w' : _cimg_mp_return(reserved_label[(int)'w']!=~0U?reserved_label[(int)'w']:18); + case 'x' : _cimg_mp_return(reserved_label[(int)'x']!=~0U?reserved_label[(int)'x']:_cimg_mp_slot_x); + case 'y' : _cimg_mp_return(reserved_label[(int)'y']!=~0U?reserved_label[(int)'y']:_cimg_mp_slot_y); + case 'z' : _cimg_mp_return(reserved_label[(int)'z']!=~0U?reserved_label[(int)'z']:_cimg_mp_slot_z); + case 'u' : + if (reserved_label[(int)'u']!=~0U) _cimg_mp_return(reserved_label[(int)'u']); + _cimg_mp_scalar2(mp_u,0,1); + case 'g' : + if (reserved_label[(int)'g']!=~0U) _cimg_mp_return(reserved_label[(int)'g']); + _cimg_mp_scalar0(mp_g); + case 'i' : + if (reserved_label[(int)'i']!=~0U) _cimg_mp_return(reserved_label[(int)'i']); + _cimg_mp_scalar0(mp_i); + case 'I' : + _cimg_mp_op("Variable 'I'"); + if (reserved_label[(int)'I']!=~0U) _cimg_mp_return(reserved_label[(int)'I']); + if (!imgin._spectrum) _cimg_mp_return(0); + need_input_copy = true; + pos = vector(imgin._spectrum); + CImg::vector((ulongT)mp_Joff,pos,0,0,imgin._spectrum).move_to(code); + _cimg_mp_return(pos); + case 'R' : + if (reserved_label[(int)'R']!=~0U) _cimg_mp_return(reserved_label[(int)'R']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,0,0,0); + case 'G' : + if (reserved_label[(int)'G']!=~0U) _cimg_mp_return(reserved_label[(int)'G']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,1,0,0); + case 'B' : + if (reserved_label[(int)'B']!=~0U) _cimg_mp_return(reserved_label[(int)'B']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,2,0,0); + case 'A' : + if (reserved_label[(int)'A']!=~0U) _cimg_mp_return(reserved_label[(int)'A']); + need_input_copy = true; + _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,3,0,0); + } + 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[(int)*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; // Index 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); + pos = scalar(); + CImg::vector((ulongT)mp_vector_neq,pos,arg1,p1,arg2,p2,11,1).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]); + _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); + pos = scalar(); + CImg::vector((ulongT)mp_vector_eq,pos,arg1,p1,arg2,p2,11,1).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]); + _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 index, 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[(int)*ss]==~0U || + !_cimg_mp_is_vector(reserved_label[(int)*ss]))) { // Image value as a vector + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0::vector((ulongT)(is_relative?mp_list_Joff:mp_list_Ioff), + 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[(int)*ss]==~0U || + !_cimg_mp_is_vector(reserved_label[(int)*ss]))) { // Image value as a scalar + if (*ss2=='#') { // Index specified + s0 = ss3; while (s0ss && (*s0!='[' || level[s0 - expr._data]!=clevel)) --s0; + if (s0>ss) { // Vector value + arg1 = compile(ss,s0,depth1,0,is_single); + if (_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 index + s0 = ++s1; while (s0::vector((ulongT)mp_vector_crop,pos,arg1,p1,arg2,arg3,arg4).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::vector((ulongT)mp_date,pos,_cimg_mp_size(pos), + arg1,arg1==~0U?~0U:_cimg_mp_size(arg1), + arg2,arg2==~0U?~0U:_cimg_mp_size(arg2)).move_to(code); + _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()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,2,0); + pos = scalar(); + CImg::vector((ulongT)mp_fsize,pos,arg1,(ulongT)_cimg_mp_size(arg1)).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()'"); + arg1 = compile(ss6,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,2,0); + pos = scalar(); + CImg::vector((ulongT)mp_isdir,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"isfile(",7)) { // Is file? + _cimg_mp_op("Function 'isfile()'"); + arg1 = compile(ss7,se1,depth1,0,is_single); + _cimg_mp_check_type(arg1,1,2,0); + pos = scalar(); + CImg::vector((ulongT)mp_isfile,pos,arg1,(ulongT)_cimg_mp_size(arg1)).move_to(code); + _cimg_mp_return(pos); + } + + 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 (s1::vector((ulongT)mp_stov,pos,arg1,p1,arg2,arg3).move_to(code); + _cimg_mp_return(pos); + } + + if (!std::strncmp(ss,"svd(",4)) { // Matrix SVD + _cimg_mp_op("Function 'svd()'"); + s1 = ss4; while (s1expr._data?ss - 4:expr._data; + cimg::strellipsize(s0,64); + 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; // Index 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 + siz_out = (unsigned int)mp.opcode[2], + siz_arg1 = (unsigned int)mp.opcode[4], + siz_arg2 = (unsigned int)mp.opcode[6]; + double *ptr_out = &_mp_arg(1) + (siz_out?1:0); + const double + *ptr_arg1 = siz_arg1==~0U?0:&_mp_arg(3) + (siz_arg1?1:0), + *ptr_arg2 = siz_arg2==~0U?0:&_mp_arg(5) + 1; + + if (!ptr_arg2) { // No filename specified + if (!siz_arg1) return cimg::date((unsigned int)*ptr_arg1); + if (siz_arg1==~0U) for (unsigned int k = 0; k::nan(); + } + + // Filename specified. + CImg ss(siz_arg2 + 1); + cimg_forX(ss,i) ss[i] = (char)ptr_arg2[i]; + ss.back() = 0; + if (!siz_arg1) return cimg::fdate(ss,(unsigned int)*ptr_arg1); + for (unsigned int k = 0; k::nan(); + } + + static double mp_debug(_cimg_math_parser& mp) { + CImg expr(mp.opcode[2] - 4); + const ulongT *ptrs = mp.opcode._data + 4; + cimg_for(expr,ptrd,char) *ptrd = (char)*(ptrs++); + 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, is_outlined = false; + float r1 = 0, r2 = 0, angle = 0, opacity = 1; + unsigned int i = 4, pattern = ~0U; + int x0 = 0, y0 = 0; + if (i>=i_end) is_invalid_arguments = true; + else { + 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 double *ptrs = &_mp_arg(2) + 1; + const ulongT siz = (ulongT)mp.opcode[3]; + CImg ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::fsize(ss); + } + + static double mp_g(_cimg_math_parser& mp) { + cimg::unused(mp); + return cimg::grand(&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 ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::is_directory(ss); + } + + static double mp_isin(_cimg_math_parser& mp) { + const unsigned int i_end = (unsigned int)mp.opcode[2]; + const double val = _mp_arg(3); + for (unsigned int i = 4; i::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_isfile(_cimg_math_parser& mp) { + const double *ptrs = &_mp_arg(2) + 1; + const ulongT siz = (ulongT)mp.opcode[3]; + CImg ss(siz + 1); + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + return (double)cimg::is_file(ss); + } + + 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,1,k,1,1,true) = S; + CImg(ptrd + k,k,l,1,1,true) = U; + CImg(ptrd + k + k*l,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, is_outlined = false; + if (!is_invalid_arguments) { + int nbv = (int)_mp_arg(4); + if (!nbv) is_invalid_arguments = true; + else { + if (nbv<0) { nbv = -nbv; is_outlined = true; } + CImg points(nbv,2,1,1,0); + CImg color(img._spectrum,1,1,1,0); + float opacity = 1; + unsigned int i = 5, pattern=~0U; + 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); + ptrs+=1 + ind; + cimg_forX(ss,i) ss[i] = (char)ptrs[i]; + ss.back() = 0; + + const char *s = ss._data; + while (*s<=32) ++s; + const bool is_negative = *s=='-'; + if (is_negative || *s=='+') ++s; + int err = 0; + char sep; + + if (*s=='0' && (s[1]=='x' || s[1]=='X') && s[2]>32) { // Hexadecimal number + unsigned int ival; + err = cimg_sscanf(s + 2,"%x%c",&ival,&sep); + if (err>0) val = (double)ival; + } else if (*s>32) { // Decimal number + err = cimg_sscanf(s,"%lf%c",&val,&sep); +#if cimg_OS==2 + // Check for +/-NaN and +/-inf as Microsoft's sscanf() version is not able + // to read those particular values. + if (!err && (*s=='i' || *s=='I' || *s=='n' || *s=='N')) { + if (!cimg::strncasecmp(s,"inf",3)) { val = cimg::type::inf(); err = 1 + (s[3]!=0); } + else if (!cimg::strncasecmp(s,"nan",3)) { val = cimg::type::nan(); err = 1 + (s[3]!=0); } + } +#endif + } + if (err<=0 || (is_strict && err!=1)) return cimg::type::nan(); + if (is_negative) val = -val; + 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 *ptrd = &_mp_arg(1) + 1; + const double *ptrs = &_mp_arg(2) + 1; + const longT + length = (longT)mp.opcode[3], + start = (longT)_mp_arg(4), + sublength = (longT)mp.opcode[5], + step = (longT)_mp_arg(6); + if (start<0 || start + step*(sublength-1)>=length) + throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Value accessor '[]': " + "Out-of-bounds sub-vector request " + "(length: %ld, start: %ld, sub-length: %ld, step: %ld).", + mp.imgin.pixel_type(),length,start,sublength,step); + ptrs+=start; + if (step==1) std::memcpy(ptrd,ptrs,sublength*sizeof(double)); + else for (longT k = 0; k::nan(); + } + + static double mp_vector_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); + 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 { + +#ifdef cimg_use_lapack + int INFO = (int)use_LU, N = _width, LWORK = 4*N, *const IPIV = new int[N]; + Tfloat + *const lapA = new Tfloat[N*N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::getrf(N,lapA,IPIV,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetrf_() returned error code %d.", + cimg_instance, + INFO); + else { + cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "invert(): LAPACK function dgetri_() returned error code %d.", + cimg_instance, + INFO); + } + if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N + l]); else fill(0); + delete[] IPIV; delete[] lapA; delete[] WORK; +#else + if (use_LU) { // LU-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.size()==1) return (*this)/=A[0]; + if (A._width==2 && A._height==2 && _height==2) { + const double a = (double)A[0], b = (double)A[1], c = (double)A[2], d = (double)A[3], + fa = std::fabs(a), fb = std::fabs(b), fc = std::fabs(c), fd = std::fabs(d), + det = a*d - b*c, fM = cimg::max(fa,fb,fc,fd); + if (fM==fa) cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det; + (*this)(k,0) = (T)((u - b*y)/a); (*this)(k,1) = (T)y; + } else if (fM==fc) cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det; + (*this)(k,0) = (T)((v - d*y)/c); (*this)(k,1) = (T)y; + } else if (fM==fb) cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det; + (*this)(k,0) = (T)x; (*this)(k,1) = (T)((u - a*x)/b); + } else cimg_forX(*this,k) { + const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det; + (*this)(k,0) = (T)x; (*this)(k,1) = (T)((v - c*x)/d); + } + return *this; + } + if (_width!=1) { // Process column-by-column + CImg res(_width,A._width); + cimg_forX(*this,i) res.draw_image(i,get_column(i).solve(A)); + return res.move_to(*this); + } + + if (A._width==A._height) { // Square linear system + +#ifdef cimg_use_lapack + char TRANS = 'N'; + int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N]; + 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 + 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 { + typedef _cimg_Ttfloat Ttfloat; + return CImg(*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(); return *this; } + if (_width!=_height || _depth>1 || _spectrum>1) + throw CImgInstanceException(_cimg_instance + "eigen(): Instance is not a square matrix.", + cimg_instance); + val.assign(1,_width); + vec.assign(_width,_width); + + if (_width==1) { val[0] = cimg::abs((*this)[0]); vec[0] = 1; return *this; } + if (_width==2) { + const double + a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], + e = a + d, f = std::sqrt(std::max(e*e - 4*(a*d - b*c),0.0)), + l1 = 0.5*(e - f), l2 = 0.5*(e + f), + n = std::sqrt(cimg::sqr(l2 - a) + b*b); + val[0] = (t)l2; + val[1] = (t)l1; + if (n>0) { vec[0] = (t)(b/n); vec[2] = (t)((l2 - a)/n); } else { vec[0] = 1; vec[2] = 0; } + vec[1] = -vec[2]; + vec[3] = vec[0]; + return *this; + } + +#ifdef cimg_use_lapack + char JOB = 'V', UPLO = 'U'; + int N = _width, LWORK = 4*N, INFO; + Tfloat + *const lapA = new Tfloat[N*N], + *const lapW = new Tfloat[N], + *const WORK = new Tfloat[LWORK]; + cimg_forXY(*this,k,l) lapA[k*N + l] = (Tfloat)((*this)(k,l)); + cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO); + if (INFO) + cimg::warn(_cimg_instance + "symmetric_eigen(): LAPACK library function dsyev_() returned error code %d.", + cimg_instance, + INFO); + if (!INFO) { + cimg_forY(val,i) val(i) = (T)lapW[N - 1 -i]; + cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N - 1 - k)*N + l]); + } else { val.fill(0); vec.fill(0); } + delete[] lapA; delete[] lapW; delete[] WORK; + +#else + CImg V(_width,_width); + Tfloat M = 0, m = (Tfloat)min_max(M), maxabs = cimg::max((Tfloat)1,cimg::abs(m),cimg::abs(M)); + (CImg(*this,false)/=maxabs).SVD(vec,val,V,false); + if (maxabs!=1) val*=maxabs; + + bool is_ambiguous = false; + float eig = 0; + cimg_forY(val,p) { // Check for ambiguous cases + if (val[p]>eig) eig = (float)val[p]; + t scal = 0; + cimg_forY(vec,y) scal+=vec(p,y)*V(p,y); + if (cimg::abs(scal)<0.9f) is_ambiguous = true; + if (scal<0) val[p] = -val[p]; + } + if (is_ambiguous) { + ++(eig*=2); + SVD(vec,val,V,false,40,eig); + val-=eig; + } + + CImg permutations; // Sort eigenvalues in decreasing order + CImg tmp(_width); + val.sort(permutations,false); + cimg_forY(vec,k) { + cimg_forY(permutations,y) tmp(y) = vec(permutations(y),k); + std::memcpy(vec.data(0,k),tmp._data,sizeof(t)*_width); + } +#endif + return *this; + } + + //! 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; + + bool return0 = false; + cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=512)) + cimg_forX(*this,i) { + Tfloat vmax = 0; + cimg_forX(*this,j) { + const Tfloat tmp = cimg::abs((*this)(j,i)); + if (tmp>vmax) vmax = tmp; + } + if (vmax==0) return0 = true; else vv[i] = 1/vmax; + } + if (return0) { indx.fill(0); return fill(0); } + + cimg_forX(*this,j) { + for (int i = 0; i=vmax) { vmax = tmp; imax = i; } + } + if (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 index of starting node %u is higher " + "than number of nodes %u.", + pixel_type(),starting_node,nb_nodes); + CImg dist(1,nb_nodes,1,1,cimg::type::max()); + 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 Index of the starting node. + \param ending_node Index of the ending node. + \param previous_node Array that gives the previous node index in the path to the starting node + (optional parameter). + \return Array of distances of each node to the starting node. + \note image instance corresponds to the adjacency matrix of the graph. + **/ + 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_index(p0,indices1,indices2,xi,nxi)), + i1 = (tf)(_isoline3d_index(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_index(const unsigned int edge, const CImg& indices1, const CImg& indices2, + const unsigned int x, const unsigned int nx) { + switch (edge) { + case 0 : return (int)indices1(x,0); + case 1 : return (int)indices1(nx,1); + case 2 : return (int)indices2(x,0); + case 3 : return (int)indices1(x,1); + } + return 0; + } + + //! 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_index(p0,indices1,indices2,xi,yi,nxi,nyi)), + i1 = (tf)(_isosurface3d_index(p1,indices1,indices2,xi,yi,nxi,nyi)), + i2 = (tf)(_isosurface3d_index(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_index(const unsigned int edge, const CImg& indices1, const CImg& indices2, + const unsigned int x, const unsigned int y, + const unsigned int nx, const unsigned int ny) { + switch (edge) { + 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.height()!=2) + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set (%u,%u,%u,%u).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum); + 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) 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,pattern); + bool ninit_hatch = true; + switch (points._height) { + case 0 : case 1 : + throw CImgArgumentException(_cimg_instance + "draw_polygon(): Invalid specified point set (%u,%u,%u,%u).", + cimg_instance, + points._width,points._height,points._depth,points._spectrum); + 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,true); + } + + //! 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,false); + 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, const bool is_filled) { + if (is_empty() || (!is_filled && !pattern)) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "draw_ellipse(): Specified color is (null).", + cimg_instance); + if (r1<0 || r2<0) return *this; + if (r1*r2<=0) return draw_point(x0,y0,color,opacity); + if (r1==r2 && (float)(int)r1==r1) { + if (is_filled) return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity); + else return draw_circle(x0,y0,(int)cimg::round(r1),color,opacity,pattern); + } + + 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); + + if (is_filled) { + cimg_init_scanline(color,opacity); + const float + 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 points((unsigned int)cimg::round(6*rmax),2); + cimg_forX(points,k) { + const float + ang = (float)(2*cimg::PI*k/points._width), + X = (float)(r1*std::cos(ang)), + Y = (float)(r2*std::sin(ang)); + points(k,0) = (int)cimg::round(x0 + (X*u - Y*v)); + points(k,1) = (int)cimg::round(y0 + (X*v + Y*u)); + } + draw_polygon(points,color,opacity,pattern); + } + return *this; + } + + //! Draw a filled 2D circle. + /** + \param x0 X-coordinate of the circle center. + \param y0 Y-coordinate of the circle center. + \param radius Circle radius. + \param color Pointer to \c spectrum() consecutive values, defining the drawing color. + \param opacity Drawing opacity. + \note + - Circle version of the Bresenham's algorithm is used. + **/ + template + 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) { + if (pattern!=~0U) return draw_ellipse(x0,y0,radius,radius,0,color,opacity,pattern); + if (is_empty()) return *this; + if (!color) + throw CImgArgumentException(_cimg_instance + "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[(int)' ']._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 int is_down, ...) { + CImg tmp(2048); + std::va_list ap; va_start(ap,is_down); + cimg_vsnprintf(tmp,tmp._width,text,ap); va_end(ap); + CImg 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; + +#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,g_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 + } + } + } 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/2)%_width), + Y0 = (int)((XYZ?XYZ[1]:_height/2)%_height), + Z0 = (int)((XYZ?XYZ[2]:_depth/2)%_depth), + X1 =-1, Y1 = -1, Z1 = -1, + X3d = -1, Y3d = -1, + oX3d = X3d, oY3d = -1, + omx = -1, omy = -1; + float X = -1, Y = -1, Z = -1; + unsigned int key = 0; + + 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...",(int)text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",(int)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... ",(int)text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",(int)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",(int)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... ",0).display(disp); + screen.save(filename); + (+screen).__draw_text(" Snapshot '%s' saved. ",0,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... ",0).display(disp); + save(filename); + (+screen).__draw_text(" Instance '%s' saved. ",0,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); + } + +#ifdef cimg_use_opencv + + // Convert a continuous cv::Mat to a CImg. + static CImg _cvmat2cimg(const cv::Mat &src) { + if (src.channels()==1) return CImg(src.ptr(),src.cols,src.rows,1,1); + else if (src.channels()==3) { // BGR + CImg res(src.cols,src.rows,1,src.channels()); + const unsigned char *ptrs = src.ptr(); + unsigned char *pR = res.data(), *pG = res.data(0,0,0,1), *pB = res.data(0,0,0,2); + cimg_forXY(res,x,y) { *(pB++) = *(ptrs++); *(pG++) = *(ptrs++); *(pR++) = *(ptrs++); } + return res; + } + return CImg(src.ptr(),src.channels(),src.cols,src.rows,1,true).get_permute_axes("yzcx"); + } + + // Convert a CImg to a cv::Mat. + cv::Mat _cimg2cvmat() const { + if (is_empty()) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Instance image is empty.", + cimg_instance); + if (_spectrum==2) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Invalid number of channels (should be '1' or '3+').", + cimg_instance); + if (_depth!=1) + throw CImgInstanceException(_cimg_instance + "_cimg2cvmat() : Invalid number of slices (should be '1').", + cimg_instance); + int mat_type = -1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_8UC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_8SC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_16UC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_16SC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_32SC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_32FC1; + if (cimg::type::string()==cimg::type::string()) mat_type = CV_64FC1; + if (mat_type<0) + throw CImgInstanceException(_cimg_instance + "_cvmat2cimg() : pixel type '%s' is not supported.", + cimg_instance,pixel_type()); + cv::Mat res; + std::vector channels(_spectrum); + if (_spectrum>1) { + cimg_forC(*this,c) + channels[c] = cv::Mat(_height,_width,mat_type,_data + _width*_height*(_spectrum - 1 - c)); + cv::merge(channels,res); + } else res = cv::Mat(_height,_width,mat_type,_data).clone(); + return res; + } + +#endif + + //! Load image from a camera stream, using OpenCV. + /** + \param index Index of the camera to capture images from (from 0 to 63). + \param capture_width Width of the desired image ('0' stands for default value). + \param capture_height Height of the desired image ('0' stands for default value). + \param skip_frames Number of frames to skip before the capture. + \param release_camera Tells if the camera ressource must be released at the end of the method. + **/ + CImg& load_camera(const unsigned int index=0, + const unsigned int capture_width=0, const unsigned int capture_height=0, + const unsigned int skip_frames=0, const bool release_camera=true) { +#ifdef cimg_use_opencv + if (index>=64) + throw CImgArgumentException(_cimg_instance + "load_camera(): Invalid request for camera #%u " + "(no more than 100 cameras can be managed simultaneously).", + cimg_instance, + index); + static cv::VideoCapture *captures[64] = { 0 }; + static unsigned int captures_w[64], captures_h[64]; + if (release_camera) { + cimg::mutex(9); + if (captures[index]) captures[index]->release(); + delete captures[index]; + captures[index] = 0; + captures_w[index] = captures_h[index] = 0; + cimg::mutex(9,0); + return *this; + } + if (!captures[index]) { + cimg::mutex(9); + captures[index] = new cv::VideoCapture(index); + captures_w[index] = captures_h[index] = 0; + if (!captures[index]->isOpened()) { + delete captures[index]; + captures[index] = 0; + cimg::mutex(9,0); + throw CImgIOException(_cimg_instance + "load_camera(): Failed to initialize camera #%u.", + cimg_instance, + index); + } + cimg::mutex(9,0); + } + cimg::mutex(9); + if (capture_width!=captures_w[index]) { + captures[index]->set(CV_CAP_PROP_FRAME_WIDTH,capture_width); + captures_w[index] = capture_width; + } + if (capture_height!=captures_h[index]) { + captures[index]->set(CV_CAP_PROP_FRAME_HEIGHT,capture_height); + captures_h[index] = capture_height; + } + for (unsigned int i = 0; igrab(); + cv::Mat cvimg; + captures[index]->read(cvimg); + if (cvimg.empty()) assign(); else _cvmat2cimg(cvimg).move_to(*this); + cimg::mutex(9,0); + return *this; +#else + cimg::unused(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 index=0, + const unsigned int capture_width=0, const unsigned int capture_height=0, + const unsigned int skip_frames=0, const bool release_camera=true) { + return CImg().load_camera(index,capture_width,capture_height,skip_frames,release_camera); + } + + //! 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 + 1)/2; + _XYZ[1] = (unsigned int)(y0 + y1 + 1)/2; + _XYZ[2] = (unsigned int)(z0 + z1 + 1)/2; + } + x0 = 0; y0 = 0; z0 = 0; x1 = width() - 1; y1 = height() - 1; z1 = depth() - 1; + disp.resize(cimg_fitscreen(_width,_height,_depth),false); + oldw = disp._width; oldh = disp._height; + resize_disp = true; + reset_view = false; + } + if (!x0 && !y0 && !z0 && x1==width() - 1 && y1==height() - 1 && z1==depth() - 1) { + 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... ",0).display(disp); + visu.save(filename); + (+visu).__draw_text(" Snapshot '%s' saved. ",0,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyG : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .off file + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.off",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving object... ",0).display(disp); + vertices.save_off(reverse_primitives?reverse_primitives:primitives,colors,filename); + (+visu).__draw_text(" Object '%s' saved. ",0,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .cimg file + static unsigned int snap_number = 0; + std::FILE *file; + do { + +#ifdef cimg_use_zlib + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); +#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... ",0).display(disp); + vertices.get_object3dtoCImg3d(reverse_primitives?reverse_primitives:primitives,colors,opacities). + save(filename); + (+visu).__draw_text(" Object '%s' saved. ",0,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + +#ifdef cimg_use_board + case cimg::keyP : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .EPS file + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.eps",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving EPS snapshot... ",0).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. ",0,filename._data).display(disp); + disp.set_key(key,false); key = 0; + } break; + case cimg::keyV : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) { // Save object as a .SVG file + static unsigned int snap_number = 0; + std::FILE *file; + do { + cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.svg",snap_number++); + if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); + } while (file); + (+visu).__draw_text(" Saving SVG snapshot... ",0,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. ",0,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 Index 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 Index 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 Index of the image element. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index 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 Index 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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 Index of the image element to access. + \param x X-coordinate of the pixel value. + \param y Y-coordinate of the pixel value. + \param z Z-coordinate of the pixel value. + \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 index. + /** + \param n Index of the checked image. + **/ + bool containsN(const int n) const { + if (is_empty()) return false; + return n>=0 && n<(int)_width; + } + + //! 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 oindex0 = -1, oindex1 = -1, index0 = -1, index1 = -1; + bool is_clicked = false, is_selected = false, text_down = false, update_display = true; + unsigned int key = 0; + + 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/2,src._height/2,src._depth/2). + move_to(res); + const unsigned int h = CImgDisplay::_fitscreen(res._width,res._height,1,128,-85,true); + res.resize(x1 - x0,std::max(32U,h*disp._height/max_height),1,res._spectrum==1?3:-100); + 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 || oindex0!=index0 || oindex1!=index1) { + if (index0>=0 && index1>=0) { + visu.assign(visu0,false); + const int indm = std::min(index0,index1), indM = std::max(index0,index1); + for (int ind = indm; ind<=indM; ++ind) if (positions(ind,0)>=0) { + visu.draw_rectangle(positions(ind,0),positions(ind,1),positions(ind,2),positions(ind,3), + background_color,0.2f); + 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",(int)text_down, + orig + indm,orig + indM,indM - indm + 1); + else visu.__draw_text(" Image #%u (%u,%u,%u,%u)",(int)text_down, + orig + index0, + _data[index0]._width, + _data[index0]._height, + _data[index0]._depth, + _data[index0]._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 index = -1; + + if (xm>=0) { + index = (int)indices(axis=='x'?xm:ym); + if (disp.button()&1) { + if (!is_clicked) { is_clicked = true; oindex0 = index0; index0 = index; } + oindex1 = index1; index1 = index; + if (!feature_type) is_selected = true; + } else { + if (!is_clicked) { oindex0 = oindex1 = index0; index0 = index1 = index; } + else is_selected = true; + } + } else { + if (is_clicked) { + if (!(disp.button()&1)) { is_clicked = is_selected = false; index0 = index1 = -1; } + else index1 = -1; + } else index0 = index1 = -1; + } + + if (disp.button()&4) { is_clicked = is_selected = false; index0 = index1 = -1; } + if (disp.button()&2 && exit_on_rightbutton) { is_selected = true; index1 = index0 = -1; } + if (disp.wheel() && exit_on_wheel) is_selected = true; + + CImg filename(32); + 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... ",(int)text_down).display(disp); + visu0.save(filename); + (+visu0).__draw_text(" Snapshot '%s' saved. ",(int)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... ",(int)text_down).display(disp); + save(filename); + (+visu0).__draw_text(" Instance '%s' saved. ",(int)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(index0,index1),std::max(index0,index1)); + else res.fill(index0); + } + 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 (can be higher than the actual number of frames, e.g. '~0U'). + \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 cv::VideoCapture *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); + captures[index]->release(); + delete captures[index]; + captures[index] = 0; + positions[index] = 0; + filenames[index].assign(); + if (last_used_index==index) last_used_index = -1; + index = -1; + cimg::mutex(9,0); + } 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] = new cv::VideoCapture(filename); + positions[index] = 0; + if (!captures[index]->isOpened()) { + delete captures[index]; + captures[index] = 0; + cimg::mutex(9,0); + cimg::fclose(cimg::fopen(filename,"rb")); // Check file availability + throw CImgIOException(_cimglist_instance + "load_video(): File '%s', unable to detect format of video file.", + cimglist_instance,filename); + } + CImg::string(filename).move_to(filenames[index]); + cimg::mutex(9,0); + } + + cimg::mutex(9); + const unsigned int nb_frames = (unsigned int)std::max(0.,captures[index]->get(CV_CAP_PROP_FRAME_COUNT)); + cimg::mutex(9,0); + assign(); + + // Skip frames if requested. + bool go_on = true; + unsigned int &pos = positions[index]; + while (posgrab()) { cimg::mutex(9,0); go_on = false; break; } + cimg::mutex(9,0); + ++pos; + } + + // Read and convert frames. + const unsigned int _last_frame = std::min(nb_frames?nb_frames - 1:~0U,last_frame); + while (go_on && pos<=_last_frame) { + cv::Mat cvimg; + cimg::mutex(9); + if (captures[index]->read(cvimg)) { CImg::_cvmat2cimg(cvimg).move_to(*this); ++pos; } + else go_on = false; + cimg::mutex(9,0); + if (go_on) + for (unsigned int i = 1; go_on && igrab()) go_on = false; + cimg::mutex(9,0); + } + } + + if (!go_on || (nb_frames && pos>=nb_frames)) { // Close video stream when necessary + cimg::mutex(9); + captures[index]->release(); + delete 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 cv::VideoWriter *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); + const char + *const _codec = codec && *codec?codec:cimg_OS==2?"mpeg":"mp4v", + codec0 = cimg::uppercase(_codec[0]), + codec1 = _codec[0]?cimg::uppercase(_codec[1]):0, + codec2 = _codec[1]?cimg::uppercase(_codec[2]):0, + codec3 = _codec[2]?cimg::uppercase(_codec[3]):0; + cimg::mutex(9); + writers[index] = new cv::VideoWriter(filename,_cimg_fourcc(codec0,codec1,codec2,codec3),fps,cv::Size(W,H)); + if (!writers[index]->isOpened()) { + delete writers[index]; + writers[index] = 0; + cimg::mutex(9,0); + throw CImgIOException(_cimglist_instance + "save_video(): File '%s', unable to initialize video writer with codec '%c%c%c%c'.", + cimglist_instance,filename, + codec0,codec1,codec2,codec3); + } + CImg::string(filename).move_to(filenames[index]); + sizes(index,0) = W; + sizes(index,1) = H; + cimg::mutex(9,0); + } + + if (!is_empty()) { + const unsigned int W = sizes(index,0), H = sizes(index,1); + cimg::mutex(9); + cimglist_for(*this,l) { + CImg &src = _data[l]; + if (src.is_empty()) + cimg::warn(_cimglist_instance + "save_video(): Skip empty frame %d for file '%s'.", + cimglist_instance,l,filename); + if (src._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) + writers[index]->write(CImg(src)._cimg2cvmat()); + else { + CImg _src(src,false); + _src.channels(0,std::min(_src._spectrum - 1,2U)).resize(W,H); + _src.resize(W,H,1,3,_src._spectrum==1); + writers[index]->write(_src._cimg2cvmat()); + } + } + cimg::mutex(9,0); + } + + cimg::mutex(9); + if (!keep_open) { + delete writers[index]; + writers[index] = 0; + filenames[index].assign(); + sizes(index,0) = sizes(index,1) = 0; + last_used_index = -1; + } else last_used_index = index; + cimg::mutex(9,0); + + 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[(int)' '].resize(font[(int)'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 Index of clicked button (from \c 0 to \c 5), or \c -1 if the dialog window has been closed by the user. + \note + - Up to 6 buttons can be defined in the dialog window. + - The function returns when a user clicked one of the button or closed the dialog window. + - If a button text is set to 0, the corresponding button (and the 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_Status +#define Status int +#endif +#ifdef _cimg_redefine_Success +#define Success 0 +#endif +#ifdef _cimg_redefine_min +#define min(a,b) (((a)<(b))?(a):(b)) +#endif +#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/tira/image/bmp.h b/tira/image/bmp.h new file mode 100644 index 0000000..e444dcb --- /dev/null +++ b/tira/image/bmp.h @@ -0,0 +1,268 @@ +#ifndef STIM_BMP_H +#define STIM_BMP_H + +#include +#include + +namespace stim { +#pragma pack(1) + typedef unsigned int DWORD; + typedef unsigned short WORD; + typedef signed int LONG; + typedef struct tagBITMAPFILEHEADER { + WORD bfType; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; + } BITMAPFILEHEADER, *PBITMAPFILEHEADER; + + const unsigned int DIB_BITMAPCOREHEADER = 12; + const unsigned int DIB_OS21XBITMAPHEADER = 16; + const unsigned int DIB_BITMAPINFOHEADER = 40; + const unsigned int DIB_BITMAPV2INFOHEADER = 52; + const unsigned int DIB_BITMAPV3INFOHEADER = 56; + const unsigned int DIB_OS22XBITMAPHEADER = 64; + const unsigned int DIB_BITMAPV4HEADER = 108; + const unsigned int DIB_BITMAPV5HEADER = 124; + + typedef struct tagBITMAPCOREHEADER { + DWORD bcSize; + WORD bcWidth; + WORD bcHeight; + WORD bcPlanes; + WORD bcBitCount; + } BITMAPCOREHEADER, *PBITMAPCOREHEADER; + + typedef struct tagBITMAPINFOHEADER { + DWORD biSize; //40 bytes + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; + } BITMAPINFOHEADER, *PBITMAPINFOHEADER; + + // From FileFormat.info + typedef struct { + DWORD Size; /* Size of this header in bytes */ + LONG Width; /* Image width in pixels */ + LONG Height; /* Image height in pixels */ + WORD Planes; /* Number of color planes */ + WORD BitsPerPixel; /* Number of bits per pixel */ + DWORD Compression; /* Compression methods used */ + DWORD SizeOfBitmap; /* Size of bitmap in bytes */ + LONG HorzResolution; /* Horizontal resolution in pixels per meter */ + LONG VertResolution; /* Vertical resolution in pixels per meter */ + DWORD ColorsUsed; /* Number of colors in the image */ + DWORD ColorsImportant; /* Minimum number of important colors */ + /* Fields added for Windows 4.x follow this line */ + + DWORD RedMask; /* Mask identifying bits of red component */ + DWORD GreenMask; /* Mask identifying bits of green component */ + DWORD BlueMask; /* Mask identifying bits of blue component */ + DWORD AlphaMask; /* Mask identifying bits of alpha component */ + DWORD CSType; /* Color space type */ + LONG RedX; /* X coordinate of red endpoint */ + LONG RedY; /* Y coordinate of red endpoint */ + LONG RedZ; /* Z coordinate of red endpoint */ + LONG GreenX; /* X coordinate of green endpoint */ + LONG GreenY; /* Y coordinate of green endpoint */ + LONG GreenZ; /* Z coordinate of green endpoint */ + LONG BlueX; /* X coordinate of blue endpoint */ + LONG BlueY; /* Y coordinate of blue endpoint */ + LONG BlueZ; /* Z coordinate of blue endpoint */ + DWORD GammaRed; /* Gamma red coordinate scale value */ + DWORD GammaGreen; /* Gamma green coordinate scale value */ + DWORD GammaBlue; /* Gamma blue coordinate scale value */ + } WIN4XBITMAPHEADER; + + typedef struct { + DWORD bV5Size; + LONG bV5Width; + LONG bV5Height; + WORD bV5Planes; + WORD bV5BitCount; + DWORD bV5Compression; + DWORD bV5SizeImage; + LONG bV5XPelsPerMeter; + LONG bV5YPelsPerMeter; + DWORD bV5ClrUsed; + DWORD bV5ClrImportant; + DWORD bV5RedMask; + DWORD bV5GreenMask; + DWORD bV5BlueMask; + DWORD bV5AlphaMask; + DWORD bV5CSType; + LONG RedX; /* X coordinate of red endpoint */ + LONG RedY; /* Y coordinate of red endpoint */ + LONG RedZ; /* Z coordinate of red endpoint */ + LONG GreenX; /* X coordinate of green endpoint */ + LONG GreenY; /* Y coordinate of green endpoint */ + LONG GreenZ; /* Z coordinate of green endpoint */ + LONG BlueX; /* X coordinate of blue endpoint */ + LONG BlueY; /* Y coordinate of blue endpoint */ + LONG BlueZ; /* Z coordinate of blue endpoint */ + DWORD bV5GammaRed; + DWORD bV5GammaGreen; + DWORD bV5GammaBlue; + DWORD bV5Intent; + DWORD bV5ProfileData; + DWORD bV5ProfileSize; + DWORD bV5Reserved; + } BITMAPV5HEADER, *PBITMAPV5HEADER; + + + //compression methods + const unsigned int STIM_BI_RGB = 0; + const unsigned int STIM_BI_BITFIELDS = 3; + + class bmp { + std::ifstream file; + public: + unsigned int dib_header_size; + size_t bit_pos; // start position (relative to the beginning of the file) of the bitmap bits + size_t total_size; //total size of the bitmap file (in bytes) + size_t width; + size_t height; + int channels; + int bits_per_pixel; + unsigned int compression; + + size_t bytes() { + return width * height * bits_per_pixel / 8; + } + void read_bmpFileHeader() { + BITMAPFILEHEADER file_header; + file.read((char*)&file_header, sizeof(BITMAPFILEHEADER)); + bit_pos = file_header.bfOffBits; + total_size = file_header.bfSize; + } + void read_bmpCoreHeader() { + tagBITMAPCOREHEADER header; + file.read((char*)&header, sizeof(tagBITMAPCOREHEADER)); + width = header.bcWidth; + height = header.bcHeight; + bits_per_pixel = header.bcBitCount; + compression = 0; + } + void read_bmpInfoHeader() { + tagBITMAPINFOHEADER info_header; + file.read((char*)&info_header, sizeof(tagBITMAPINFOHEADER)); + width = info_header.biWidth; + height = info_header.biHeight; + bits_per_pixel = info_header.biBitCount; + compression = info_header.biCompression; + } + void read_bmpV4Header() { + WIN4XBITMAPHEADER header; + file.read((char*)&header, sizeof(WIN4XBITMAPHEADER)); + width = header.Width; + height = header.Height; + bits_per_pixel = header.BitsPerPixel; + compression = header.Compression; + } + void read_bmpV5Header() { + BITMAPV5HEADER header; + file.read((char*)&header, sizeof(BITMAPV5HEADER)); + width = header.bV5Width; + height = header.bV5Height; + bits_per_pixel = header.bV5BitCount; + compression = header.bV5Compression; + } + void read_dib() { //read the bitmap DIB information header + std::streamoff header_pos = file.tellg(); + file.read((char*)&dib_header_size, sizeof(unsigned int)); + file.seekg(header_pos); + switch (dib_header_size) { + case DIB_BITMAPCOREHEADER: read_bmpCoreHeader(); break; + case DIB_BITMAPINFOHEADER: read_bmpInfoHeader(); break; + case DIB_BITMAPV4HEADER: read_bmpV4Header(); break; + case DIB_BITMAPV5HEADER: read_bmpV5Header(); break; + default: + std::cout << "stim::bmp ERROR: this bitmap header format isn't supported" << std::endl; + exit(1); + } + } + + bool open(std::string filename) { //open the bitmap file and read the header data + file.open(filename, std::ifstream::binary); + if (!file) { + std::cout << "stim::bmp ERROR: error opening file: " << filename.c_str() << std::endl; + return false; + } + read_bmpFileHeader(); //read the file header + read_dib(); + if (compression != STIM_BI_RGB) { //check for compression + std::cout << "stim::bmp ERROR: this file is compressed, and compression is not supported" << std::endl; + return false; + } + return true; + } + void close() { + file.close(); + } + + /// Copy the bitmap data into a pre-allocated array + bool read(char* dst){ + file.seekg(bit_pos); //seek to the beginning of the data array + size_t row_bytes = width * bits_per_pixel / 8; //number of bytes in each row + size_t padding = row_bytes % 4; //calculate the padding on disk for each row (rows must be multiples of 4) + + if(file){ + for (size_t h = 0; h < height; h++) { //for each row in the image + file.read(dst + (height - h - 1) * row_bytes, row_bytes); //read the row of image data + file.seekg(padding, std::ios::cur); //seek to the end of the row on disk + if (file.eof()) std::cout << "stim::bmp ERROR: array size incorrect, end of file reached while reading bitmap." << std::endl; + else if (file.fail()) std::cout << "stim::bmp ERROR: reading bitmap array failed." << std::endl; + else if (file.bad()) std::cout << "stim::bmp ERROR: stream integrity failed while reading bitmap array" << std::endl; + } + return true; + } + else{ + std::cout<<"stim::bmp ERROR: could not read array from file."< +#include +#include //use limits and remove the MIN and MAX macros +#include +#include +#include +#include + + +#include + +namespace stim{ +/// This static class provides the STIM interface for loading, saving, and storing 2D images. +/// Data is stored in an interleaved (BIP) format (default for saving and loading is RGB). + +template +class image{ + + T* img; //pointer to the image data (interleaved RGB for color) + size_t R[3]; + bool interleaved = true; //by default the data is interleaved + + inline size_t X() const { return R[1]; } + inline size_t Y() const { return R[2]; } + inline size_t C() const { return R[0]; } + + void init(){ //initializes all variables, assumes no memory is allocated + memset(R, 0, sizeof(size_t) * 3); //set the resolution and number of channels to zero + img = NULL; + } + + void unalloc(){ //frees any resources associated with the image + if(img) free(img); //if memory has been allocated, free it + } + + + void clear(){ //clears all image data + unalloc(); //unallocate previous memory + init(); //re-initialize the variables + } + + void allocate(){ + unalloc(); + img = (T*) malloc( sizeof(T) * R[0] * R[1] * R[2] ); //allocate memory + if (img == NULL) { + std::cout << "stim::image ERROR - failed to allocate memory for image" << std::endl; + exit(1); + } + } + + void allocate(size_t x, size_t y, size_t c){ //allocate memory based on the resolution + R[0] = c; R[1] = x; R[2] = y; //set the resolution + allocate(); //allocate memory + } + + inline size_t idx(size_t x, size_t y, size_t c = 0) const { + return y * R[0] * R[1] + x * R[0] + c; + } + + /// Returns the value for "white" based on the dynamic range (assumes white is 1.0 for floating point images) + T white(){ + if (typeid(T) == typeid(double) || typeid(T) == typeid(float)) + return (T)1.0; + else + return std::numeric_limits::max(); + } + +public: + + size_t bytes() { return size() * sizeof(T); } + + /// Default constructor - creates an empty image object + image(){ init(); } //initialize all variables to zero, don't allocate any memory + + /// Constructor with a filename - loads the specified file + image(std::string filename){ //constructor initialize the image with an image file + init(); + load(filename); + } + + /// Create a new image from scratch given a number of samples and channels + image(size_t x, size_t y = 1, size_t c = 1){ + init(); + allocate(x, y, c); + } + + /// Create a new image with the data given in 'data' + image(T* data, size_t x, size_t y, size_t c = 1){ + init(); + allocate(x, y, c); + memcpy(img, data, bytes()); + } + + /// Copy constructor - duplicates an image object + image(const stim::image& I){ + init(); + allocate(I.X(), I.Y(), I.C()); + memcpy(img, I.img, bytes()); + } + + /// Destructor - clear memory + ~image(){ + free(img); + } + + stim::image& operator=(const stim::image& I){ + if(&I == this) //handle self-assignment + return *this; + init(); + allocate(I.X(), I.Y(), I.C()); + memcpy(img, I.img, bytes()); + return *this; + } + + //determines if a filename represents a valid file format that can be loaded/saved + static bool test_filename(std::string f) { + stim::filename fname = f; + std::string ext = fname.extension(); + if (ext == "bmp" || + ext == "jpg" || + ext == "png" || + ext == "pbm" || + ext == "tif" ) + return true; + else + return false; + } + + //save a Netpbm file + void load_netpbm(std::string filename) { + std::ifstream infile(filename.c_str(), std::ios::in | std::ios::binary); //open an output file + if (!infile) { + std::cout << "Error opening input file in image::load_netpbm()" << std::endl; + exit(1); + } + + size_t nc; //allocate space for the number of channels + char format[2]; //allocate space to hold the image format tag + infile.read(format, 2); //read the image format tag + infile.seekg(1, std::ios::cur); //skip the newline character + + if (format[0] != 'P') { + std::cout << "Error in image::load_netpbm() - file format tag is invalid: " << format[0] << format[1] << std::endl; + exit(1); + } + if (format[1] == '5') nc = 1; //get the number of channels from the format flag + else if (format[1] == '6') nc = 3; + else { + std::cout << "Error in image::load_netpbm() - file format tag is invalid: " << format[0] << format[1] << std::endl; + exit(1); + } + + unsigned char c; //stores a character + while (infile.peek() == '#') { //if the next character indicates the start of a comment + while (true) { + c = infile.get(); + if (c == 0x0A) break; + } + } + + std::string sw; //create a string to store the width of the image + while(true){ + c = infile.get(); //get a single character + if (c == ' ') break; //exit if we've encountered a space + sw.push_back(c); //push the character on to the string + } + size_t w = atoi(sw.c_str()); //convert the string into an integer + + std::string sh; + while (true) { + c = infile.get(); + if (c == 0x0A) break; + sh.push_back(c); + } + + while (true) { //skip the maximum value + c = infile.get(); + if (c == 0x0A) break; + } + size_t h = atoi(sh.c_str()); //convert the string into an integer + + allocate(w, h, nc); //allocate space for the image + unsigned char* buffer = (unsigned char*)malloc(w * h * nc); //create a buffer to store the read data + infile.read((char*)buffer, size()); //copy the binary data from the file to the image + infile.close(); //close the file + for (size_t n = 0; n < size(); n++) img[n] = (T)buffer[n]; //copy the buffer data into the image + free(buffer); //free the buffer array + } + + + //Copy N data points from source to dest, casting while doing so + template + void type_copy(S* source, D* dest, size_t N) { + if (typeid(S) == typeid(D)) //if both types are the same + memcpy(dest, source, N * sizeof(S)); //just use a memcpy + for (size_t n = 0; n < N; n++) //otherwise, iterate through each element + dest[n] = (D)source[n]; //copy and cast + } + /// Load an image from a file + void load(std::string filename){ + //Use CImg to load the file + cimg_library::CImg cimg(filename.c_str()); //create a CImg object for the image file + + set_noninterleaved(cimg.data(), cimg.width(), cimg.height(), cimg.spectrum()); + } + + + + //save a Netpbm file + void save_netpbm(std::string filename) { + std::ofstream outfile(filename.c_str(), std::ios::out | std::ios::binary); //open an output file + if(!outfile) { + std::cout << "Error generating output file in image::save_netpbm()" << std::endl; + exit(1); + } + if (sizeof(T) != 1) { + std::cout << "Error in image::save_netpbm() - data type must be 8-bit integer." << std::endl; + exit(1); + } + std::string format; + if (channels() == 1) outfile << "P5" << (char)0x0A; //output P5 if the file is grayscale + else if (channels() == 3) outfile << "P6" << (char)0x0A; //output P6 if the file is color + else { + std::cout << "Error in image::save_netpbm() - data must be grayscale or RGB." << std::endl; + exit(1); + } + size_t w = width(); + size_t h = height(); + outfile << w << " " << h << (char)0x0A; //save the width and height + outfile << "255" << (char)0x0A; //output the maximum value + outfile.write((const char*)img, size()); //write the binary data + outfile.close(); + } + + //save a file + void save(std::string filename){ + stim::filename file(filename); + if (file.extension() == "raw" || file.extension() == "") { + std::ofstream outfile(filename.c_str(), std::ios::binary); + outfile.write((char*)img, sizeof(T) * R[0] * R[1] * R[2]); + outfile.close(); + } + else { + cimg_library::CImg cimg((unsigned int)R[1], (unsigned int)R[2], 1, (unsigned int)R[0]); + get_noninterleaved(cimg.data()); + cimg.save(filename.c_str()); + } + } + + /// Returns an image cast to the specified format + template + image convert() { + + image new_image(R[1], R[2], R[0]); //create a new image with the destination data type + + size_t ni = R[0] * R[1] * R[2]; //calculate the number of data points in the image + + double inmax = (std::numeric_limits::max)(); //get the maximum value for the input image + double outmax = (std::numeric_limits::max)(); //get the maximum value for the output image + for (size_t i = 0; i < ni; i++) { //for each pixel in the image + if (img[i] > outmax) new_image(i) = outmax; //if the source pixel is greater than the maximum destination pixel, set the output to maximum + else new_image(i) = img[i]; //otherwise, copy the source value and cast it to the destination value + } + return new_image; + } + + void set_interleaved(T* buffer, size_t width, size_t height, size_t channels){ + allocate(width, height, channels); + memcpy(img, buffer, bytes()); + } + + //create an image from an interleaved buffer + void set_interleaved_rgb(T* buffer, size_t width, size_t height){ + set_interleaved(buffer, width, height, 3); + } + + void set_interleaved_bgr(T* buffer, size_t width, size_t height){ + allocate(width, height, 3); + T value; + size_t i; + for(size_t c = 0; c < C(); c++){ //copy directly + for(size_t y = 0; y < Y(); y++){ + for(size_t x = 0; x < X(); x++){ + i = y * X() * C() + x * C() + (2-c); + value = buffer[i]; + img[idx(x, y, c)] = value; + } + } + } + } + + void set_interleaved(T* buffer, size_t width, size_t height){ + set_interleaved_rgb(buffer, width, height); + } + + //copies data in the given channel order as a non-interleaved image + void set_noninterleaved(T* data, size_t width, size_t height, size_t chan) { + allocate(width, height, chan); + + //for each channel + for (size_t y = 0; y < Y(); y++) { + for (size_t x = 0; x < X(); x++) { + for (size_t c = 0; c < C(); c++) { + img[idx(x, y, c)] = data[c * Y() * X() + y * X() + x]; + } + } + } + } + + void get_interleaved_bgr(T* data){ + //for each channel + T* source; + if (C() == 3) { + source = img; //if the image has 3 channels, interleave all three + } + else if (C() == 4) { + source = img + X() * Y(); //if the image has 4 channels, skip the alpha channel + } + else { + throw std::runtime_error("ERROR: a BGR image must be 3 or 4 channels"); //throw an error if any other number of channels is provided + } + for(size_t y = 0; y < Y(); y++){ + for(size_t x = 0; x < X(); x++){ + for(size_t c = 0; c < 3; c++){ + data[y * X() * 3 + x * 3 + (2-c)] = source[y*3*R[1] + x*3 + c]; + } + } + } + } + + void get_interleaved_rgb(T* data){ + memcpy(data, img, bytes()); + } + + //copies data in the given channel order as a non-interleaved image + void get_noninterleaved(T* data){ + //for each channel + for(size_t y = 0; y < Y(); y++){ + for(size_t x = 0; x < X(); x++){ + for(size_t c = 0; c < C(); c++){ + data[c * Y() * X() + y * X() + x] = img[idx(x, y, c)]; + } + } + } + } + + /// Return an image representing a specified channel + /// @param c is the channel to be returned + image channel(size_t c) const { + image r(X(), Y(), 1); //create a new image + for(size_t x = 0; x < X(); x++){ + for(size_t y = 0; y < Y(); y++){ + r.img[r.idx(x, y, 0)] = img[idx(x, y, c)]; + } + } + return r; + } + + /// Returns an std::vector containing each channel as a separate image + std::vector< image > split() const { + std::vector< image > r; //create an image array + r.resize(C()); //create images for each channel + + for (size_t c = 0; c < C(); c++) { //for each channel + r[c] = channel(c); //copy the channel image to the array + } + return r; + } + + /// Merge a series of single-channel images into a multi-channel image + void merge(std::vector< image >& list) { + size_t x = list[0].width(); //calculate the size of the image + size_t y = list[0].height(); + allocate(x, y, list.size()); //re-allocate the image + for (size_t c = 0; c < list.size(); c++) //for each channel + set_channel(list[c].channel(0).data(), c); //insert the channel into the output image + } + + T& operator()(size_t x, size_t y, size_t c = 0){ + return img[idx(x, y, c)]; + } + + /// This function returns a pixel reference based on a 1D index into the image + T& operator()(size_t i) { + return img[i]; + } + + /// Set all elements in the image to a given scalar value + + /// @param v is the value used to set all values in the image + void set_all(T v) { //set all elements of the image to a given value v + size_t N = size(); + for (size_t n = 0; n < N; n++) img[n] = v; + } + image operator=(T v){ + set_all(v); + return *this; + } + + /// invert the image, given a specified maximum value (ex. maxval = 255, I' = 255 - I) + /*image invert(T maxval) { + image result(width(), height(), channels()); //create a new image + size_t N = size(); //get the number of elements in the image + for (size_t n = 0; n < N; n++) + result.data()[n] = maxval - img[n]; //perform the inversion and save the result to the new image + return result; + }*/ + + /// Stretch the contrast of the image such that the minimum and maximum intensity match the given values + image stretch(T low, T high) { + T maxval = maxv(); + T minval = minv(); + image result = *this; //create a new image for output + if (maxval == minval) { //if the minimum and maximum values are the same, return an image composed of low + result = low; + return result; + } + + size_t N = size(); //get the number of values in the image + T range = maxval - minval; //calculate the current range of the image + T desired_range = high - low; //calculate the desired range of the image + for (size_t n = 0; n < N; n++) { //for each element in the image + result.data()[n] = desired_range * (img[n] - minval) / range + low; + } + return result; + } + + /// Add a border of width w with the given value around the image + /// @param w specifies the total size of the border + /// @param T is the pixel value (all channels will be the same) + image border(size_t w, T value = 0) { + image result(width() + w * 2, height() + w * 2, channels()); //create an output image + result = value; //assign the border value to all pixels in the new image + for (size_t y = 0; y < height(); y++) { //for each pixel in the original image + for (size_t x = 0; x < width(); x++) { + size_t n = (y + w) * (width() + w * 2) + x + w; //calculate the index of the corresponding pixel in the result image + size_t n0 = idx(x,y); //calculate the index for this pixel in the original image + result.data()[n] = img[n0]; // copy the original image to the result image afer the border area + } + } + return result; + } + + /// Adds curcular padding for the specified number of pixels - in this case replicating the boundary pixels + image pad_replicate(size_t p) { + image result(width() + p * 2, height() + p * 2, channels()); //create an output image + result = 0; + //result = value; //assign the border value to all pixels in the new image + for (size_t y = 0; y < height(); y++) { //for each pixel in the original image + for (size_t x = 0; x < width(); x++) { + size_t n = (y + p) * (width() + p * 2) + x + p; //calculate the index of the corresponding pixel in the result image + size_t n0 = idx(x, y); //calculate the index for this pixel in the original image + result.data()[n] = img[n0]; // copy the original image to the result image afer the border area + } + } + size_t l = p; + size_t r = p + width() - 1; + size_t t = p; + size_t b = p + height() - 1; + for (size_t y = 0; y < p; y++) for (size_t x = l; x <= r; x++) result(x, y) = result(x, t); //pad the top + for (size_t y = b + 1; y < result.height(); y++) for (size_t x = l; x <= r; x++) result(x, y) = result(x, b); //pad the bottom + for (size_t y = t; y <= b; y++) for (size_t x = 0; x < l; x++) result(x, y) = result(l, y); //pad the left + for (size_t y = t; y <= b; y++) for (size_t x = r+1; x < result.width(); x++) result(x, y) = result(r, y); //pad the right + for (size_t y = 0; y < t; y++) for (size_t x = 0; x < l; x++) result(x, y) = result(l, t); //pad the top left + for (size_t y = 0; y < t; y++) for (size_t x = r+1; x < result.width(); x++) result(x, y) = result(r, t); //pad the top right + for (size_t y = b+1; y < result.height(); y++) for (size_t x = 0; x < l; x++) result(x, y) = result(l, b); //pad the bottom left + for (size_t y = b+1; y < result.height(); y++) for (size_t x = r + 1; x < result.width(); x++) result(x, y) = result(r, b); //pad the bottom right + return result; + } + + /// Copy the given data to the specified channel + + /// @param c is the channel number that the data will be copied to + /// @param buffer is a pointer to the image to be copied to channel c + + void set_channel(T* buffer, size_t c){ + size_t x, y; + for(y = 0; y < Y(); y++){ + for(x = 0; x < X(); x++){ + img[idx(x, y, c)] = buffer[y * X() + x]; + } + } + } + + /// Set the specified channel to a constant value + + /// @param c is the channel number that the data will be copied to + /// @param buffer is a pointer to the image to be copied to channel c + + void set_channel(T val, size_t c) { + size_t x, y; + for (y = 0; y < Y(); y++) { + for (x = 0; x < X(); x++) { + img[idx(x, y, c)] = val; + } + } + } + + size_t channels() const{ + return C(); + } + + size_t width() const{ + return X(); + } + + size_t height() const{ + return Y(); + } + + T* data(){ + return img; + } + + //returns the size (number of values) of the image + size_t size(){ return C() * X() * Y(); } + + /// Returns the number of nonzero values + size_t nnz(){ + + size_t N = X() * Y() * C(); + + size_t nz = 0; + for(size_t n = 0; n < N; n++) + if(img[n] != 0) nz++; + + return nz; //return the number of nonzero pixels + + } + + //this function returns indices of pixels that have nonzero values + std::vector sparse_idx(){ + + std::vector s; //allocate an array + s.resize(nnz()); //allocate space in the array + + size_t N = size(); + //size_t C = channels(); + + //T* ptr = img.data(); //get a pointer to the image data + + size_t i = 0; + for(size_t n = 0; n < N; n++){ + if(img[n] != 0){ + s[i] = n; + i++; + } + } + + return s; //return the index list + } + + + /// Returns the maximum pixel value in the image + T maxv(){ + T max_val = img[0]; //initialize the maximum value to the first one + size_t N = size(); //get the number of pixels + + for (size_t n=0; n max_val){ //if the value is higher than the current max + max_val = img[n]; + } + } + return max_val; + } + + /// Returns the maximum pixel value in the image + T minv(){ + T min_val = img[0]; //initialize the maximum value to the first one + size_t N = size(); //get the number of pixels + + for (size_t n=0; n invert(T white_val){ + size_t N = size(); //calculate the total number of values in the image + image r(X(), Y(), C()); //allocate space for the resulting image + for(size_t n = 0; n < N; n++) + r.img[n] = white_val - img[n]; //perform the inversion + + return r; //return the inverted image + } + + image crop(size_t x0, size_t y0, size_t w, size_t h){ + image result(w, h, C()); //create the output cropped image + + size_t srci; + size_t dsti; + size_t line_bytes = w * C(); //calculate the number of bytes in a line + for (size_t yi = 0; yi < h; yi++) { //for each row in the cropped image + srci = (y0 + yi) * X() * C() + x0 * C(); //calculate the source index + dsti = yi * w * C(); //calculate the destination index + memcpy(&result.img[dsti], &img[srci], line_bytes); //copy the data + } + return result; + } + + //crop regions given by an array of 1D index values + std::vector< image > crop_idx(size_t w, size_t h, std::vector idx) { + std::vector< image > result(idx.size()); //create an array of image files to return + for (size_t i = 0; i < idx.size(); i++) { //for each specified index point + size_t y = idx[i] / X(); //calculate the y coordinate from the 1D index (center of ROI) + size_t x = idx[i] - y * X(); //calculate the x coordinate (center of ROI) + y -= w / 2; //update x and y values to reflect the lower corner of the ROI + x -= h / 2; + result[i] = crop(x, y, w, h); //get the cropped image and store it in the result array + } + return result; + } + + //operator functions + image operator+(image rhs) { + size_t N = size(); //calculate the total number of values in the image + image r(X(), Y(), C()); //allocate space for the resulting image + for (size_t n = 0; n < N; n++) + r.img[n] = img[n] + rhs.img[n]; //perform the inversion + return r; //return the inverted image + } + + image srgb2lab(){ + std::cout<<"ERROR stim::image::srgb2lab - function has been broken, re-implement."< convolve2(image mask){ + std::cout<<"ERROR stim::image::convolve2 - function has been broken, and shouldn't really be in here."< rotate(float angle, float cx, float cy){ + std::cout<<"ERROR stim::image::rotate - function has been broken, and shouldn't really be in here."< + operator image() { + image r(X(), Y(), C()); //create a new image + std::copy(img, img + size(), r.data()); //copy and cast the data + return r; //return the new image + } + +}; + +}; //end namespace stim + + +#endif diff --git a/tira/image/image_contour_detection.h b/tira/image/image_contour_detection.h new file mode 100644 index 0000000..055d06a --- /dev/null +++ b/tira/image/image_contour_detection.h @@ -0,0 +1,16 @@ + +//stim::image gaussian_derivative_filter_odd(stim::image image, int r, unsigned int sigma_n, float theta); +//stim::image func_mPb_theta(stim::image img, float theta, int* r, float* alpha, int s); +//stim::image func_mPb(stim::image img, unsigned int theta_n, int* r, float* alpha, int s); + +stim::image Gd1(stim::image image, int r, unsigned int sigma_n); +stim::image Gd2(stim::image image, int r, unsigned int sigma_n); +stim::image Gd_odd(stim::image image, int r, unsigned int sigma_n, float theta); +stim::image Gd_even(stim::image image, int r, unsigned int sigma_n, float theta); +stim::image Gd_center(stim::image image, int r, unsigned int sigma_n); + +stim::image textons(stim::image image, unsigned int theta_n); +stim::image kmeans(stim::image textons, unsigned int K); +stim::image Pb(stim::image image, int r, unsigned int sigma_n); +stim::image cPb(stim::image img, int* r, float* alpha, int s); +stim::image tPb(stim::image img, int* r, float* alpha, unsigned int theta_n, unsigned int bin_n, int s, unsigned int K); \ No newline at end of file diff --git a/tira/math/bessel - Copy.h b/tira/math/bessel - Copy.h new file mode 100644 index 0000000..ab5dabf --- /dev/null +++ b/tira/math/bessel - Copy.h @@ -0,0 +1,1515 @@ +#ifndef RTS_BESSEL_H +#define RTS_BESSEL_H + +#define _USE_MATH_DEFINES +#include +#include "../math/complex.h" +#define eps 1e-15 +#define el 0.5772156649015329 + + +namespace stim{ + +static complex cii(0.0,1.0); +static complex cone(1.0,0.0); +static complex czero(0.0,0.0); + +template< typename P > +P gamma(P x) +{ + int i,k,m; + P ga,gr,r,z; + + static P g[] = { + 1.0, + 0.5772156649015329, + -0.6558780715202538, + -0.420026350340952e-1, + 0.1665386113822915, + -0.421977345555443e-1, + -0.9621971527877e-2, + 0.7218943246663e-2, + -0.11651675918591e-2, + -0.2152416741149e-3, + 0.1280502823882e-3, + -0.201348547807e-4, + -0.12504934821e-5, + 0.1133027232e-5, + -0.2056338417e-6, + 0.6116095e-8, + 0.50020075e-8, + -0.11812746e-8, + 0.1043427e-9, + 0.77823e-11, + -0.36968e-11, + 0.51e-12, + -0.206e-13, + -0.54e-14, + 0.14e-14}; + + if (x > 171.0) return 1e308; // This value is an overflow flag. + if (x == (int)x) { + if (x > 0.0) { + ga = 1.0; // use factorial + for (i=2;i 1.0) { + z = fabs(x); + m = (int)z; + r = 1.0; + for (k=1;k<=m;k++) { + r *= (z-k); + } + z -= m; + } + else + z = x; + gr = g[24]; + for (k=23;k>=0;k--) { + gr = gr*z+g[k]; + } + ga = 1.0/(gr*z); + if (fabs(x) > 1.0) { + ga *= r; + if (x < 0.0) { + ga = -M_PI/(x*ga*sin(M_PI*x)); + } + } + } + return ga; +} + +template +int bessjy01a(P x,P &j0,P &j1,P &y0,P &y1, + P &j0p,P &j1p,P &y0p,P &y1p) +{ + P x2,r,ec,w0,w1,r0,r1,cs0,cs1; + P cu,p0,q0,p1,q1,t1,t2; + int k,kz; + static P a[] = { + -7.03125e-2, + 0.112152099609375, + -0.5725014209747314, + 6.074042001273483, + -1.100171402692467e2, + 3.038090510922384e3, + -1.188384262567832e5, + 6.252951493434797e6, + -4.259392165047669e8, + 3.646840080706556e10, + -3.833534661393944e12, + 4.854014686852901e14, + -7.286857349377656e16, + 1.279721941975975e19}; + static P b[] = { + 7.32421875e-2, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1, + 5.513358961220206e2, + -1.825775547429318e4, + 8.328593040162893e5, + -5.006958953198893e7, + 3.836255180230433e9, + -3.649010818849833e11, + 4.218971570284096e13, + -5.827244631566907e15, + 9.476288099260110e17, + -1.792162323051699e20}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947, + 1.215978918765359e2, + -3.302272294480852e3, + 1.276412726461746e5, + -6.656367718817688e6, + 4.502786003050393e8, + -3.833857520742790e10, + 4.011838599133198e12, + -5.060568503314727e14, + 7.572616461117958e16, + -1.326257285320556e19}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1, + -6.038440767050702e2, + 1.971837591223663e4, + -8.902978767070678e5, + 5.310411010968522e7, + -4.043620325107754e9, + 3.827011346598605e11, + -4.406481417852278e13, + 6.065091351222699e15, + -9.833883876590679e17, + 1.855045211579828e20}; + + if (x < 0.0) return 1; + if (x == 0.0) { + j0 = 1.0; + j1 = 0.0; + y0 = -1e308; + y1 = -1e308; + j0p = 0.0; + j1p = 0.5; + y0p = 1e308; + y1p = 1e308; + return 0; + } + x2 = x*x; + if (x <= 12.0) { + j0 = 1.0; + r = 1.0; + for (k=1;k<=30;k++) { + r *= -0.25*x2/(k*k); + j0 += r; + if (fabs(r) < fabs(j0)*1e-15) break; + } + j1 = 1.0; + r = 1.0; + for (k=1;k<=30;k++) { + r *= -0.25*x2/(k*(k+1)); + j1 += r; + if (fabs(r) < fabs(j1)*1e-15) break; + } + j1 *= 0.5*x; + ec = log(0.5*x)+el; + cs0 = 0.0; + w0 = 0.0; + r0 = 1.0; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + r0 *= -0.25*x2/(k*k); + r = r0 * w0; + cs0 += r; + if (fabs(r) < fabs(cs0)*1e-15) break; + } + y0 = M_2_PI*(ec*j0-cs0); + cs1 = 1.0; + w1 = 0.0; + r1 = 1.0; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + r1 *= -0.25*x2/(k*(k+1)); + r = r1*(2.0*w1+1.0/(k+1)); + cs1 += r; + if (fabs(r) < fabs(cs1)*1e-15) break; + } + y1 = M_2_PI * (ec*j1-1.0/x-0.25*x*cs1); + } + else { + if (x >= 50.0) kz = 8; // Can be changed to 10 + else if (x >= 35.0) kz = 10; // " " 12 + else kz = 12; // " " 14 + t1 = x-M_PI_4; + p0 = 1.0; + q0 = -0.125/x; + for (k=0;k +int bessjy01b(P x,P &j0,P &j1,P &y0,P &y1, + P &j0p,P &j1p,P &y0p,P &y1p) +{ + P t,t2,dtmp,a0,p0,q0,p1,q1,ta0,ta1; + if (x < 0.0) return 1; + if (x == 0.0) { + j0 = 1.0; + j1 = 0.0; + y0 = -1e308; + y1 = -1e308; + j0p = 0.0; + j1p = 0.5; + y0p = 1e308; + y1p = 1e308; + return 0; + } + if(x <= 4.0) { + t = x/4.0; + t2 = t*t; + j0 = ((((((-0.5014415e-3*t2+0.76771853e-2)*t2-0.0709253492)*t2+ + 0.4443584263)*t2-1.7777560599)*t2+3.9999973021)*t2 + -3.9999998721)*t2+1.0; + j1 = t*(((((((-0.1289769e-3*t2+0.22069155e-2)*t2-0.0236616773)*t2+ + 0.1777582922)*t2-0.8888839649)*t2+2.6666660544)*t2- + 3.999999971)*t2+1.9999999998); + dtmp = (((((((-0.567433e-4*t2+0.859977e-3)*t2-0.94855882e-2)*t2+ + 0.0772975809)*t2-0.4261737419)*t2+1.4216421221)*t2- + 2.3498519931)*t2+1.0766115157)*t2+0.3674669052; + y0 = M_2_PI*log(0.5*x)*j0+dtmp; + dtmp = (((((((0.6535773e-3*t2-0.0108175626)*t2+0.107657607)*t2- + 0.7268945577)*t2+3.1261399273)*t2-7.3980241381)*t2+ + 6.8529236342)*t2+0.3932562018)*t2-0.6366197726; + y1 = M_2_PI*log(0.5*x)*j1+dtmp/x; + } + else { + t = 4.0/x; + t2 = t*t; + a0 = sqrt(M_2_PI/x); + p0 = ((((-0.9285e-5*t2+0.43506e-4)*t2-0.122226e-3)*t2+ + 0.434725e-3)*t2-0.4394275e-2)*t2+0.999999997; + q0 = t*(((((0.8099e-5*t2-0.35614e-4)*t2+0.85844e-4)*t2- + 0.218024e-3)*t2+0.1144106e-2)*t2-0.031249995); + ta0 = x-M_PI_4; + j0 = a0*(p0*cos(ta0)-q0*sin(ta0)); + y0 = a0*(p0*sin(ta0)+q0*cos(ta0)); + p1 = ((((0.10632e-4*t2-0.50363e-4)*t2+0.145575e-3)*t2 + -0.559487e-3)*t2+0.7323931e-2)*t2+1.000000004; + q1 = t*(((((-0.9173e-5*t2+0.40658e-4)*t2-0.99941e-4)*t2 + +0.266891e-3)*t2-0.1601836e-2)*t2+0.093749994); + ta1 = x-0.75*M_PI; + j1 = a0*(p1*cos(ta1)-q1*sin(ta1)); + y1 = a0*(p1*sin(ta1)+q1*cos(ta1)); + } + j0p = -j1; + j1p = j0-j1/x; + y0p = -y1; + y1p = y0-y1/x; + return 0; +} +template +int msta1(P x,int mp) +{ + P a0,f0,f1,f; + int i,n0,n1,nn; + + a0 = fabs(x); + n0 = (int)(1.1*a0)+1; + f0 = 0.5*log10(6.28*n0)-n0*log10(1.36*a0/n0)-mp; + n1 = n0+5; + f1 = 0.5*log10(6.28*n1)-n1*log10(1.36*a0/n1)-mp; + for (i=0;i<20;i++) { + nn = (int)(n1-(n1-n0)/(1.0-f0/f1)); + f = 0.5*log10(6.28*nn)-nn*log10(1.36*a0/nn)-mp; + if (std::abs(nn-n1) < 1) break; + n0 = n1; + f0 = f1; + n1 = nn; + f1 = f; + } + return nn; +} +template +int msta2(P x,int n,int mp) +{ + P a0,ejn,hmp,f0,f1,f,obj; + int i,n0,n1,nn; + + a0 = fabs(x); + hmp = 0.5*mp; + ejn = 0.5*log10(6.28*n)-n*log10(1.36*a0/n); + if (ejn <= hmp) { + obj = mp; + n0 = (int)(1.1*a0); + if (n0 < 1) n0 = 1; + } + else { + obj = hmp+ejn; + n0 = n; + } + f0 = 0.5*log10(6.28*n0)-n0*log10(1.36*a0/n0)-obj; + n1 = n0+5; + f1 = 0.5*log10(6.28*n1)-n1*log10(1.36*a0/n1)-obj; + for (i=0;i<20;i++) { + nn = (int)(n1-(n1-n0)/(1.0-f0/f1)); + f = 0.5*log10(6.28*nn)-nn*log10(1.36*a0/nn)-obj; + if (std::abs(nn-n1) < 1) break; + n0 = n1; + f0 = f1; + n1 = nn; + f1 = f; + } + return nn+10; +} +// +// INPUT: +// double x -- argument of Bessel function of 1st and 2nd kind. +// int n -- order +// +// OUPUT: +// +// int nm -- highest order actually computed (nm <= n) +// double jn[] -- Bessel function of 1st kind, orders from 0 to nm +// double yn[] -- Bessel function of 2nd kind, orders from 0 to nm +// double j'n[]-- derivative of Bessel function of 1st kind, +// orders from 0 to nm +// double y'n[]-- derivative of Bessel function of 2nd kind, +// orders from 0 to nm +// +// Computes Bessel functions of all order up to 'n' using recurrence +// relations. If 'nm' < 'n' only 'nm' orders are returned. +// +template +int bessjyna(int n,P x,int &nm,P *jn,P *yn, + P *jnp,P *ynp) +{ + P bj0,bj1,f,f0,f1,f2,cs; + int i,k,m,ecode; + + nm = n; + if ((x < 0.0) || (n < 0)) return 1; + if (x < 1e-15) { + for (i=0;i<=n;i++) { + jn[i] = 0.0; + yn[i] = -1e308; + jnp[i] = 0.0; + ynp[i] = 1e308; + } + jn[0] = 1.0; + jnp[1] = 0.5; + return 0; + } + ecode = bessjy01a(x,jn[0],jn[1],yn[0],yn[1],jnp[0],jnp[1],ynp[0],ynp[1]); + if (n < 2) return 0; + bj0 = jn[0]; + bj1 = jn[1]; + if (n < (int)0.9*x) { + for (k=2;k<=n;k++) { + jn[k] = 2.0*(k-1.0)*bj1/x-bj0; + bj0 = bj1; + bj1 = jn[k]; + } + } + else { + m = msta1(x,200); + if (m < n) nm = m; + else m = msta2(x,n,15); + f2 = 0.0; + f1 = 1.0e-100; + for (k=m;k>=0;k--) { + f = 2.0*(k+1.0)/x*f1-f2; + if (k <= nm) jn[k] = f; + f2 = f1; + f1 = f; + } + if (fabs(bj0) > fabs(bj1)) cs = bj0/f; + else cs = bj1/f2; + for (k=0;k<=nm;k++) { + jn[k] *= cs; + } + } + for (k=2;k<=nm;k++) { + jnp[k] = jn[k-1]-k*jn[k]/x; + } + f0 = yn[0]; + f1 = yn[1]; + for (k=2;k<=nm;k++) { + f = 2.0*(k-1.0)*f1/x-f0; + yn[k] = f; + f0 = f1; + f1 = f; + } + for (k=2;k<=nm;k++) { + ynp[k] = yn[k-1]-k*yn[k]/x; + } + return 0; +} +// +// Same input and output conventions as above. Different recurrence +// relations used for 'x' < 300. +// +template +int bessjynb(int n,P x,int &nm,P *jn,P *yn, + P *jnp,P *ynp) +{ + P t1,t2,f,f1,f2,bj0,bj1,bjk,by0,by1,cu,s0,su,sv; + P ec,bs,byk,p0,p1,q0,q1; + static P a[] = { + -0.7031250000000000e-1, + 0.1121520996093750, + -0.5725014209747314, + 6.074042001273483}; + static P b[] = { + 0.7324218750000000e-1, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1}; + + int i,k,m; + nm = n; + if ((x < 0.0) || (n < 0)) return 1; + if (x < 1e-15) { + for (i=0;i<=n;i++) { + jn[i] = 0.0; + yn[i] = -1e308; + jnp[i] = 0.0; + ynp[i] = 1e308; + } + jn[0] = 1.0; + jnp[1] = 0.5; + return 0; + } + if (x <= 300.0 || n > (int)(0.9*x)) { + if (n == 0) nm = 1; + m = msta1(x,200); + if (m < nm) nm = m; + else m = msta2(x,nm,15); + bs = 0.0; + su = 0.0; + sv = 0.0; + f2 = 0.0; + f1 = 1.0e-100; + for (k = m;k>=0;k--) { + f = 2.0*(k+1.0)/x*f1 - f2; + if (k <= nm) jn[k] = f; + if ((k == 2*(int)(k/2)) && (k != 0)) { + bs += 2.0*f; +// su += pow(-1,k>>1)*f/(double)k; + su += (-1)*((k & 2)-1)*f/(P)k; + } + else if (k > 1) { +// sv += pow(-1,k>>1)*k*f/(k*k-1.0); + sv += (-1)*((k & 2)-1)*(P)k*f/(k*k-1.0); + } + f2 = f1; + f1 = f; + } + s0 = bs+f; + for (k=0;k<=nm;k++) { + jn[k] /= s0; + } + ec = log(0.5*x) +0.5772156649015329; + by0 = M_2_PI*(ec*jn[0]-4.0*su/s0); + yn[0] = by0; + by1 = M_2_PI*((ec-1.0)*jn[1]-jn[0]/x-4.0*sv/s0); + yn[1] = by1; + } + else { + t1 = x-M_PI_4; + p0 = 1.0; + q0 = -0.125/x; + for (k=0;k<4;k++) { + p0 += a[k]*pow(x,-2*k-2); + q0 += b[k]*pow(x,-2*k-3); + } + cu = sqrt(M_2_PI/x); + bj0 = cu*(p0*cos(t1)-q0*sin(t1)); + by0 = cu*(p0*sin(t1)+q0*cos(t1)); + jn[0] = bj0; + yn[0] = by0; + t2 = x-0.75*M_PI; + p1 = 1.0; + q1 = 0.375/x; + for (k=0;k<4;k++) { + p1 += a1[k]*pow(x,-2*k-2); + q1 += b1[k]*pow(x,-2*k-3); + } + bj1 = cu*(p1*cos(t2)-q1*sin(t2)); + by1 = cu*(p1*sin(t2)+q1*cos(t2)); + jn[1] = bj1; + yn[1] = by1; + for (k=2;k<=nm;k++) { + bjk = 2.0*(k-1.0)*bj1/x-bj0; + jn[k] = bjk; + bj0 = bj1; + bj1 = bjk; + } + } + jnp[0] = -jn[1]; + for (k=1;k<=nm;k++) { + jnp[k] = jn[k-1]-k*jn[k]/x; + } + for (k=2;k<=nm;k++) { + byk = 2.0*(k-1.0)*by1/x-by0; + yn[k] = byk; + by0 = by1; + by1 = byk; + } + ynp[0] = -yn[1]; + for (k=1;k<=nm;k++) { + ynp[k] = yn[k-1]-k*yn[k]/x; + } + return 0; + +} + +// The following routine computes Bessel Jv(x) and Yv(x) for +// arbitrary positive order (v). For negative order, use: +// +// J-v(x) = Jv(x)cos(v pi) - Yv(x)sin(v pi) +// Y-v(x) = Jv(x)sin(v pi) + Yv(x)cos(v pi) +// +template +int bessjyv(P v,P x,P &vm,P *jv,P *yv, + P *djv,P *dyv) +{ + P v0,vl,vg,vv,a,a0,r,x2,bjv0,bjv1,bjvl,f,f0,f1,f2; + P r0,r1,ck,cs,cs0,cs1,sk,qx,px,byv0,byv1,rp,xk,rq; + P b,ec,w0,w1,bju0,bju1,pv0,pv1,byvk; + int j,k,l,m,n,kz; + + x2 = x*x; + n = (int)v; + v0 = v-n; + if ((x < 0.0) || (v < 0.0)) return 1; + if (x < 1e-15) { + for (k=0;k<=n;k++) { + jv[k] = 0.0; + yv[k] = -1e308; + djv[k] = 0.0; + dyv[k] = 1e308; + if (v0 == 0.0) { + jv[0] = 1.0; + djv[1] = 0.5; + } + else djv[0] = 1e308; + } + vm = v; + return 0; + } + if (x <= 12.0) { + for (l=0;l<2;l++) { + vl = v0 + l; + bjvl = 1.0; + r = 1.0; + for (k=1;k<=40;k++) { + r *= -0.25*x2/(k*(k+vl)); + bjvl += r; + if (fabs(r) < fabs(bjvl)*1e-15) break; + } + vg = 1.0 + vl; + a = pow(0.5*x,vl)/gamma(vg); + if (l == 0) bjv0 = bjvl*a; + else bjv1 = bjvl*a; + } + } + else { + if (x >= 50.0) kz = 8; + else if (x >= 35.0) kz = 10; + else kz = 11; + for (j=0;j<2;j++) { + vv = 4.0*(j+v0)*(j+v0); + px = 1.0; + rp = 1.0; + for (k=1;k<=kz;k++) { + rp *= (-0.78125e-2)*(vv-pow(4.0*k-3.0,2.0))* + (vv-pow(4.0*k-1.0,2.0))/(k*(2.0*k-1.0)*x2); + px += rp; + } + qx = 1.0; + rq = 1.0; + for (k=1;k<=kz;k++) { + rq *= (-0.78125e-2)*(vv-pow(4.0*k-1.0,2.0))* + (vv-pow(4.0*k+1.0,2.0))/(k*(2.0*k+1.0)*x2); + qx += rq; + } + qx *= 0.125*(vv-1.0)/x; + xk = x-(0.5*(j+v0)+0.25)*M_PI; + a0 = sqrt(M_2_PI/x); + ck = cos(xk); + sk = sin(xk); + + if (j == 0) { + bjv0 = a0*(px*ck-qx*sk); + byv0 = a0*(px*sk+qx*ck); + } + else if (j == 1) { + bjv1 = a0*(px*ck-qx*sk); + byv1 = a0*(px*sk+qx*ck); + } + } + } + jv[0] = bjv0; + jv[1] = bjv1; + djv[0] = v0*jv[0]/x-jv[1]; + djv[1] = -(1.0+v0)*jv[1]/x+jv[0]; + if ((n >= 2) && (n <= (int)(0.9*x))) { + f0 = bjv0; + f1 = bjv1; + for (k=2;k<=n;k++) { + f = 2.0*(k+v0-1.0)*f1/x-f0; + jv[k] = f; + f0 = f1; + f1 = f; + } + } + else if (n >= 2) { + m = msta1(x,200); + if (m < n) n = m; + else m = msta2(x,n,15); + f2 = 0.0; + f1 = 1.0e-100; + for (k=m;k>=0;k--) { + f = 2.0*(v0+k+1.0)*f1/x-f2; + if (k <= n) jv[k] = f; + f2 = f1; + f1 = f; + } + if (fabs(bjv0) > fabs(bjv1)) cs = bjv0/f; + else cs = bjv1/f2; + for (k=0;k<=n;k++) { + jv[k] *= cs; + } + } + for (k=2;k<=n;k++) { + djv[k] = -(k+v0)*jv[k]/x+jv[k-1]; + } + if (x <= 12.0) { + if (v0 != 0.0) { + for (l=0;l<2;l++) { + vl = v0 +l; + bjvl = 1.0; + r = 1.0; + for (k=1;k<=40;k++) { + r *= -0.25*x2/(k*(k-vl)); + bjvl += r; + if (fabs(r) < fabs(bjvl)*1e-15) break; + } + vg = 1.0-vl; + b = pow(2.0/x,vl)/gamma(vg); + if (l == 0) bju0 = bjvl*b; + else bju1 = bjvl*b; + } + pv0 = M_PI*v0; + pv1 = M_PI*(1.0+v0); + byv0 = (bjv0*cos(pv0)-bju0)/sin(pv0); + byv1 = (bjv1*cos(pv1)-bju1)/sin(pv1); + } + else { + ec = log(0.5*x)+el; + cs0 = 0.0; + w0 = 0.0; + r0 = 1.0; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + r0 *= -0.25*x2/(k*k); + cs0 += r0*w0; + } + byv0 = M_2_PI*(ec*bjv0-cs0); + cs1 = 1.0; + w1 = 0.0; + r1 = 1.0; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + r1 *= -0.25*x2/(k*(k+1)); + cs1 += r1*(2.0*w1+1.0/(k+1.0)); + } + byv1 = M_2_PI*(ec*bjv1-1.0/x-0.25*x*cs1); + } + } + yv[0] = byv0; + yv[1] = byv1; + for (k=2;k<=n;k++) { + byvk = 2.0*(v0+k-1.0)*byv1/x-byv0; + yv[k] = byvk; + byv0 = byv1; + byv1 = byvk; + } + dyv[0] = v0*yv[0]/x-yv[1]; + for (k=1;k<=n;k++) { + dyv[k] = -(k+v0)*yv[k]/x+yv[k-1]; + } + vm = n + v0; + return 0; +} + +template +int bessjyv_sph(int v, P z, P &vm, P* cjv, + P* cyv, P* cjvp, P* cyvp) +{ + //first, compute the bessel functions of fractional order + bessjyv

(v + 0.5, z, vm, cjv, cyv, cjvp, cyvp); + + //iterate through each and scale + for(int n = 0; n<=v; n++) + { + + cjv[n] = cjv[n] * sqrt(stim::PI/(z * 2.0)); + cyv[n] = cyv[n] * sqrt(stim::PI/(z * 2.0)); + + cjvp[n] = -1.0 / (z * 2.0) * cjv[n] + cjvp[n] * sqrt(stim::PI / (z * 2.0)); + cyvp[n] = -1.0 / (z * 2.0) * cyv[n] + cyvp[n] * sqrt(stim::PI / (z * 2.0)); + } + + return 0; + +} + +template +int cbessjy01(complex

z,complex

&cj0,complex

&cj1, + complex

&cy0,complex

&cy1,complex

&cj0p, + complex

&cj1p,complex

&cy0p,complex

&cy1p) +{ + complex

z1,z2,cr,cp,cs,cp0,cq0,cp1,cq1,ct1,ct2,cu; + P a0,w0,w1; + int k,kz; + + static P a[] = { + -7.03125e-2, + 0.112152099609375, + -0.5725014209747314, + 6.074042001273483, + -1.100171402692467e2, + 3.038090510922384e3, + -1.188384262567832e5, + 6.252951493434797e6, + -4.259392165047669e8, + 3.646840080706556e10, + -3.833534661393944e12, + 4.854014686852901e14, + -7.286857349377656e16, + 1.279721941975975e19}; + static P b[] = { + 7.32421875e-2, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1, + 5.513358961220206e2, + -1.825775547429318e4, + 8.328593040162893e5, + -5.006958953198893e7, + 3.836255180230433e9, + -3.649010818849833e11, + 4.218971570284096e13, + -5.827244631566907e15, + 9.476288099260110e17, + -1.792162323051699e20}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947, + 1.215978918765359e2, + -3.302272294480852e3, + 1.276412726461746e5, + -6.656367718817688e6, + 4.502786003050393e8, + -3.833857520742790e10, + 4.011838599133198e12, + -5.060568503314727e14, + 7.572616461117958e16, + -1.326257285320556e19}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1, + -6.038440767050702e2, + 1.971837591223663e4, + -8.902978767070678e5, + 5.310411010968522e7, + -4.043620325107754e9, + 3.827011346598605e11, + -4.406481417852278e13, + 6.065091351222699e15, + -9.833883876590679e17, + 1.855045211579828e20}; + + a0 = abs(z); + z2 = z*z; + z1 = z; + if (a0 == 0.0) { + cj0 = cone; + cj1 = czero; + cy0 = complex

(-1e308,0); + cy1 = complex

(-1e308,0); + cj0p = czero; + cj1p = complex

(0.5,0.0); + cy0p = complex

(1e308,0); + cy1p = complex

(1e308,0); + return 0; + } + if (real(z) < 0.0) z1 = -z; + if (a0 <= 12.0) { + cj0 = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(P)(k*k); + cj0 += cr; + if (abs(cr) < abs(cj0)*eps) break; + } + cj1 = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k+1.0)); + cj1 += cr; + if (abs(cr) < abs(cj1)*eps) break; + } + cj1 *= 0.5*z1; + w0 = 0.0; + cr = cone; + cs = czero; + for (k=1;k<=40;k++) { + w0 += 1.0/k; + cr *= -0.25*z2/(P)(k*k); + cp = cr*w0; + cs += cp; + if (abs(cp) < abs(cs)*eps) break; + } + cy0 = M_2_PI*((log(0.5*z1)+el)*cj0-cs); + w1 = 0.0; + cr = cone; + cs = cone; + for (k=1;k<=40;k++) { + w1 += 1.0/k; + cr *= -0.25*z2/(k*(k+1.0)); + cp = cr*(2.0*w1+1.0/(k+1.0)); + cs += cp; + if (abs(cp) < abs(cs)*eps) break; + } + cy1 = M_2_PI*((log(0.5*z1)+el)*cj1-1.0/z1-0.25*z1*cs); + } + else { + if (a0 >= 50.0) kz = 8; // can be changed to 10 + else if (a0 >= 35.0) kz = 10; // " " " 12 + else kz = 12; // " " " 14 + ct1 = z1 - M_PI_4; + cp0 = cone; + for (k=0;k 0.0) { + cy0 += 2.0*cii*cj0; + cy1 = -(cy1+2.0*cii*cj1); + } + cj1 = -cj1; + } + cj0p = -cj1; + cj1p = cj0-cj1/z; + cy0p = -cy1; + cy1p = cy0-cy1/z; + return 0; +} + +template +int cbessjyna(int n,complex

z,int &nm,complex

*cj, + complex

*cy,complex

*cjp,complex

*cyp) +{ + complex

cbj0,cbj1,cby0,cby1,cj0,cjk,cj1,cf,cf1,cf2; + complex

cs,cg0,cg1,cyk,cyl1,cyl2,cylk,cp11,cp12,cp21,cp22; + complex

ch0,ch1,ch2; + P a0,yak,ya1,ya0,wa; + int m,k,lb,lb0; + + if (n < 0) return 1; + a0 = abs(z); + nm = n; + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cj[k] = czero; + cy[k] = complex

(-1e308,0); + cjp[k] = czero; + cyp[k] = complex

(1e308,0); + } + cj[0] = cone; + cjp[1] = complex

(0.5,0.0); + return 0; + } + cbessjy01(z,cj[0],cj[1],cy[0],cy[1],cjp[0],cjp[1],cyp[0],cyp[1]); + cbj0 = cj[0]; + cbj1 = cj[1]; + cby0 = cy[0]; + cby1 = cy[1]; + if (n <= 1) return 0; + if (n < (int)0.25*a0) { + cj0 = cbj0; + cj1 = cbj1; + for (k=2;k<=n;k++) { + cjk = 2.0*(k-1.0)*cj1/z-cj0; + cj[k] = cjk; + cj0 = cj1; + cj1 = cjk; + } + } + else { + m = msta1(a0,200); + if (m < n) nm = m; + else m = msta2(a0,n,15); + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(k+1.0)*cf1/z-cf2; + if (k <=nm) cj[k] = cf; + cf2 = cf1; + cf1 = cf; + } + if (abs(cbj0) > abs(cbj1)) cs = cbj0/cf; + else cs = cbj1/cf2; + for (k=0;k<=nm;k++) { + cj[k] *= cs; + } + } + for (k=2;k<=nm;k++) { + cjp[k] = cj[k-1]-(P)k*cj[k]/z; + } + ya0 = abs(cby0); + lb = 0; + cg0 = cby0; + cg1 = cby1; + for (k=2;k<=nm;k++) { + cyk = 2.0*(k-1.0)*cg1/z-cg0; + yak = abs(cyk); + ya1 = abs(cg0); + if ((yak < ya0) && (yak < ya1)) lb = k; + cy[k] = cyk; + cg0 = cg1; + cg1 = cyk; + } + lb0 = 0; + if ((lb > 4) && (imag(z) != 0.0)) { + while (lb != lb0) { + ch2 = cone; + ch1 = czero; + lb0 = lb; + for (k=lb;k>=1;k--) { + ch0 = 2.0*k*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp12 = ch0; + cp22 = ch2; + ch2 = czero; + ch1 = cone; + for (k=lb;k>=1;k--) { + ch0 = 2.0*k*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp11 = ch0; + cp21 = ch2; + if (lb == nm) + cj[lb+1] = 2.0*lb*cj[lb]/z-cj[lb-1]; + if (abs(cj[0]) > abs(cj[1])) { + cy[lb+1] = (cj[lb+1]*cby0-2.0*cp11/(M_PI*z))/cj[0]; + cy[lb] = (cj[lb]*cby0+2.0*cp12/(M_PI*z))/cj[0]; + } + else { + cy[lb+1] = (cj[lb+1]*cby1-2.0*cp21/(M_PI*z))/cj[1]; + cy[lb] = (cj[lb]*cby1+2.0*cp22/(M_PI*z))/cj[1]; + } + cyl2 = cy[lb+1]; + cyl1 = cy[lb]; + for (k=lb-1;k>=0;k--) { + cylk = 2.0*(k+1.0)*cyl1/z-cyl2; + cy[k] = cylk; + cyl2 = cyl1; + cyl1 = cylk; + } + cyl1 = cy[lb]; + cyl2 = cy[lb+1]; + for (k=lb+1;k +int cbessjynb(int n,complex

z,int &nm,complex

*cj, + complex

*cy,complex

*cjp,complex

*cyp) +{ + complex

cf,cf0,cf1,cf2,cbs,csu,csv,cs0,ce; + complex

ct1,cp0,cq0,cp1,cq1,cu,cbj0,cby0,cbj1,cby1; + complex

cyy,cbjk,ct2; + P a0,y0; + int k,m; + static P a[] = { + -0.7031250000000000e-1, + 0.1121520996093750, + -0.5725014209747314, + 6.074042001273483}; + static P b[] = { + 0.7324218750000000e-1, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1}; + + y0 = abs(imag(z)); + a0 = abs(z); + nm = n; + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cj[k] = czero; + cy[k] = complex

(-1e308,0); + cjp[k] = czero; + cyp[k] = complex

(1e308,0); + } + cj[0] = cone; + cjp[1] = complex

(0.5,0.0); + return 0; + } + if ((a0 <= 300.0) || (n > (int)(0.25*a0))) { + if (n == 0) nm = 1; + m = msta1(a0,200); + if (m < nm) nm = m; + else m = msta2(a0,nm,15); + cbs = czero; + csu = czero; + csv = czero; + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(k+1.0)*cf1/z-cf2; + if (k <= nm) cj[k] = cf; + if (((k & 1) == 0) && (k != 0)) { + if (y0 <= 1.0) { + cbs += 2.0*cf; + } + else { + cbs += (-1)*((k & 2)-1)*2.0*cf; + } + csu += (P)((-1)*((k & 2)-1))*cf/(P)k; + } + else if (k > 1) { + csv += (P)((-1)*((k & 2)-1)*k)*cf/(P)(k*k-1.0); + } + cf2 = cf1; + cf1 = cf; + } + if (y0 <= 1.0) cs0 = cbs+cf; + else cs0 = (cbs+cf)/cos(z); + for (k=0;k<=nm;k++) { + cj[k] /= cs0; + } + ce = log(0.5*z)+el; + cy[0] = M_2_PI*(ce*cj[0]-4.0*csu/cs0); + cy[1] = M_2_PI*(-cj[0]/z+(ce-1.0)*cj[1]-4.0*csv/cs0); + } + else { + ct1 = z-M_PI_4; + cp0 = cone; + for (k=0;k<4;k++) { + cp0 += a[k]*pow(z,-2.0*k-2.0); + } + cq0 = -0.125/z; + for (k=0;k<4;k++) { + cq0 += b[k] *pow(z,-2.0*k-3.0); + } + cu = sqrt(M_2_PI/z); + cbj0 = cu*(cp0*cos(ct1)-cq0*sin(ct1)); + cby0 = cu*(cp0*sin(ct1)+cq0*cos(ct1)); + cj[0] = cbj0; + cy[0] = cby0; + ct2 = z-0.75*M_PI; + cp1 = cone; + for (k=0;k<4;k++) { + cp1 += a1[k]*pow(z,-2.0*k-2.0); + } + cq1 = 0.375/z; + for (k=0;k<4;k++) { + cq1 += b1[k]*pow(z,-2.0*k-3.0); + } + cbj1 = cu*(cp1*cos(ct2)-cq1*sin(ct2)); + cby1 = cu*(cp1*sin(ct2)+cq1*cos(ct2)); + cj[1] = cbj1; + cy[1] = cby1; + for (k=2;k<=n;k++) { + cbjk = 2.0*(k-1.0)*cbj1/z-cbj0; + cj[k] = cbjk; + cbj0 = cbj1; + cbj1 = cbjk; + } + } + cjp[0] = -cj[1]; + for (k=1;k<=nm;k++) { + cjp[k] = cj[k-1]-(P)k*cj[k]/z; + } + if (abs(cj[0]) > 1.0) + cy[1] = (cj[1]*cy[0]-2.0/(M_PI*z))/cj[0]; + for (k=2;k<=nm;k++) { + if (abs(cj[k-1]) >= abs(cj[k-2])) + cyy = (cj[k]*cy[k-1]-2.0/(M_PI*z))/cj[k-1]; + else + cyy = (cj[k]*cy[k-2]-4.0*(k-1.0)/(M_PI*z*z))/cj[k-2]; + cy[k] = cyy; + } + cyp[0] = -cy[1]; + for (k=1;k<=nm;k++) { + cyp[k] = cy[k-1]-(P)k*cy[k]/z; + } + + return 0; +} + +template +int cbessjyva(P v,complex

z,P &vm,complex

*cjv, + complex

*cyv,complex

*cjvp,complex

*cyvp) +{ + complex

z1,z2,zk,cjvl,cr,ca,cjv0,cjv1,cpz,crp; + complex

cqz,crq,ca0,cck,csk,cyv0,cyv1,cju0,cju1,cb; + complex

cs,cs0,cr0,cs1,cr1,cec,cf,cf0,cf1,cf2; + complex

cfac0,cfac1,cg0,cg1,cyk,cp11,cp12,cp21,cp22; + complex

ch0,ch1,ch2,cyl1,cyl2,cylk; + + P a0,v0,pv0,pv1,vl,ga,gb,vg,vv,w0,w1,ya0,yak,ya1,wa; + int j,n,k,kz,l,lb,lb0,m; + + a0 = abs(z); + z1 = z; + z2 = z*z; + n = (int)v; + + + v0 = v-n; + + pv0 = M_PI*v0; + pv1 = M_PI*(1.0+v0); + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cjv[k] = czero; + cyv[k] = complex

(-1e308,0); + cjvp[k] = czero; + cyvp[k] = complex

(1e308,0); + + } + if (v0 == 0.0) { + cjv[0] = cone; + cjvp[1] = complex

(0.5,0.0); + } + else { + cjvp[0] = complex

(1e308,0); + } + vm = v; + return 0; + } + if (real(z1) < 0.0) z1 = -z; + if (a0 <= 12.0) { + for (l=0;l<2;l++) { + vl = v0+l; + cjvl = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k+vl)); + cjvl += cr; + if (abs(cr) < abs(cjvl)*eps) break; + } + vg = 1.0 + vl; + ga = gamma(vg); + ca = pow(0.5*z1,vl)/ga; + if (l == 0) cjv0 = cjvl*ca; + else cjv1 = cjvl*ca; + } + } + else { + if (a0 >= 50.0) kz = 8; + else if (a0 >= 35.0) kz = 10; + else kz = 11; + for (j=0;j<2;j++) { + vv = 4.0*(j+v0)*(j+v0); + cpz = cone; + crp = cone; + for (k=1;k<=kz;k++) { + crp = -0.78125e-2*crp*(vv-pow(4.0*k-3.0,2.0))* + (vv-pow(4.0*k-1.0,2.0))/(k*(2.0*k-1.0)*z2); + cpz += crp; + } + cqz = cone; + crq = cone; + for (k=1;k<=kz;k++) { + crq = -0.78125e-2*crq*(vv-pow(4.0*k-1.0,2.0))* + (vv-pow(4.0*k+1.0,2.0))/(k*(2.0*k+1.0)*z2); + cqz += crq; + } + cqz *= 0.125*(vv-1.0)/z1; + zk = z1-(0.5*(j+v0)+0.25)*M_PI; + ca0 = sqrt(M_2_PI/z1); + cck = cos(zk); + csk = sin(zk); + if (j == 0) { + cjv0 = ca0*(cpz*cck-cqz*csk); + cyv0 = ca0*(cpz*csk+cqz+cck); + } + else { + cjv1 = ca0*(cpz*cck-cqz*csk); + cyv1 = ca0*(cpz*csk+cqz*cck); + } + } + } + if (a0 <= 12.0) { + if (v0 != 0.0) { + for (l=0;l<2;l++) { + vl = v0+l; + cjvl = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k-vl)); + cjvl += cr; + if (abs(cr) < abs(cjvl)*eps) break; + } + vg = 1.0-vl; + gb = gamma(vg); + cb = pow(2.0/z1,vl)/gb; + if (l == 0) cju0 = cjvl*cb; + else cju1 = cjvl*cb; + } + cyv0 = (cjv0*cos(pv0)-cju0)/sin(pv0); + cyv1 = (cjv1*cos(pv1)-cju1)/sin(pv1); + } + else { + cec = log(0.5*z1)+el; + cs0 = czero; + w0 = 0.0; + cr0 = cone; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + cr0 *= -0.25*z2/(P)(k*k); + cs0 += cr0*w0; + } + cyv0 = M_2_PI*(cec*cjv0-cs0); + cs1 = cone; + w1 = 0.0; + cr1 = cone; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + cr1 *= -0.25*z2/(k*(k+1.0)); + cs1 += cr1*(2.0*w1+1.0/(k+1.0)); + } + cyv1 = M_2_PI*(cec*cjv1-1.0/z1-0.25*z1*cs1); + } + } + if (real(z) < 0.0) { + cfac0 = exp(pv0*cii); + cfac1 = exp(pv1*cii); + if (imag(z) < 0.0) { + cyv0 = cfac0*cyv0-(P)2.0*(complex

)cii*cos(pv0)*cjv0; + cyv1 = cfac1*cyv1-(P)2.0*(complex

)cii*cos(pv1)*cjv1; + cjv0 /= cfac0; + cjv1 /= cfac1; + } + else if (imag(z) > 0.0) { + cyv0 = cyv0/cfac0+(P)2.0*(complex

)cii*cos(pv0)*cjv0; + cyv1 = cyv1/cfac1+(P)2.0*(complex

)cii*cos(pv1)*cjv1; + cjv0 *= cfac0; + cjv1 *= cfac1; + } + } + cjv[0] = cjv0; + cjv[1] = cjv1; + if ((n >= 2) && (n <= (int)(0.25*a0))) { + cf0 = cjv0; + cf1 = cjv1; + for (k=2;k<= n;k++) { + cf = 2.0*(k+v0-1.0)*cf1/z-cf0; + cjv[k] = cf; + cf0 = cf1; + cf1 = cf; + } + } + else if (n >= 2) { + m = msta1(a0,200); + if (m < n) n = m; + else m = msta2(a0,n,15); + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(v0+k+1.0)*cf1/z-cf2; + if (k <= n) cjv[k] = cf; + cf2 = cf1; + cf1 = cf; + } + if (abs(cjv0) > abs(cjv1)) cs = cjv0/cf; + else cs = cjv1/cf2; + for (k=0;k<=n;k++) { + cjv[k] *= cs; + } + } + cjvp[0] = v0*cjv[0]/z-cjv[1]; + for (k=1;k<=n;k++) { + cjvp[k] = -(k+v0)*cjv[k]/z+cjv[k-1]; + } + cyv[0] = cyv0; + cyv[1] = cyv1; + ya0 = abs(cyv0); + lb = 0; + cg0 = cyv0; + cg1 = cyv1; + for (k=2;k<=n;k++) { + cyk = 2.0*(v0+k-1.0)*cg1/z-cg0; + yak = abs(cyk); + ya1 = abs(cg0); + if ((yak < ya0) && (yak< ya1)) lb = k; + cyv[k] = cyk; + cg0 = cg1; + cg1 = cyk; + } + lb0 = 0; + if ((lb > 4) && (imag(z) != 0.0)) { + while(lb != lb0) { + ch2 = cone; + ch1 = czero; + lb0 = lb; + for (k=lb;k>=1;k--) { + ch0 = 2.0*(k+v0)*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp12 = ch0; + cp22 = ch2; + ch2 = czero; + ch1 = cone; + for (k=lb;k>=1;k--) { + ch0 = 2.0*(k+v0)*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp11 = ch0; + cp21 = ch2; + if (lb == n) + cjv[lb+1] = 2.0*(lb+v0)*cjv[lb]/z-cjv[lb-1]; + if (abs(cjv[0]) > abs(cjv[1])) { + cyv[lb+1] = (cjv[lb+1]*cyv0-2.0*cp11/(M_PI*z))/cjv[0]; + cyv[lb] = (cjv[lb]*cyv0+2.0*cp12/(M_PI*z))/cjv[0]; + } + else { + cyv[lb+1] = (cjv[lb+1]*cyv1-2.0*cp21/(M_PI*z))/cjv[1]; + cyv[lb] = (cjv[lb]*cyv1+2.0*cp22/(M_PI*z))/cjv[1]; + } + cyl2 = cyv[lb+1]; + cyl1 = cyv[lb]; + for (k=lb-1;k>=0;k--) { + cylk = 2.0*(k+v0+1.0)*cyl1/z-cyl2; + cyv[k] = cylk; + cyl2 = cyl1; + cyl1 = cylk; + } + cyl1 = cyv[lb]; + cyl2 = cyv[lb+1]; + for (k=lb+1;k +int cbessjyva_sph(int v,complex

z,P &vm,complex

*cjv, + complex

*cyv,complex

*cjvp,complex

*cyvp) +{ + //first, compute the bessel functions of fractional order + cbessjyva

(v + 0.5, z, vm, cjv, cyv, cjvp, cyvp); + + //iterate through each and scale + for(int n = 0; n<=v; n++) + { + + cjv[n] = cjv[n] * sqrt(stim::PI/(z * 2.0)); + cyv[n] = cyv[n] * sqrt(stim::PI/(z * 2.0)); + + cjvp[n] = -1.0 / (z * 2.0) * cjv[n] + cjvp[n] * sqrt(stim::PI / (z * 2.0)); + cyvp[n] = -1.0 / (z * 2.0) * cyv[n] + cyvp[n] * sqrt(stim::PI / (z * 2.0)); + } + + return 0; + +} + +} //end namespace rts + + +#endif diff --git a/tira/math/bessel.h b/tira/math/bessel.h new file mode 100644 index 0000000..a3f7ec0 --- /dev/null +++ b/tira/math/bessel.h @@ -0,0 +1,1544 @@ +#ifndef RTS_BESSEL_H +#define RTS_BESSEL_H + +#define _USE_MATH_DEFINES +#include +#include "../math/complex.h" +#define eps 1e-15 +#define el 0.5772156649015329 + + +namespace stim{ + +static complex cii(0.0,1.0); +static complex cone(1.0,0.0); +static complex czero(0.0,0.0); + +template< typename P > +P gamma(P x) +{ + const P EPS = std::numeric_limits

::epsilon(); + const P FPMIN_MAG = std::numeric_limits

::min(); + const P FPMIN = std::numeric_limits

::lowest(); + const P FPMAX = std::numeric_limits

::max(); + + int i,k,m; + P ga,gr,r,z; + + static P g[] = { + 1.0, + 0.5772156649015329, + -0.6558780715202538, + -0.420026350340952e-1, + 0.1665386113822915, + -0.421977345555443e-1, + -0.9621971527877e-2, + 0.7218943246663e-2, + -0.11651675918591e-2, + -0.2152416741149e-3, + 0.1280502823882e-3, + -0.201348547807e-4, + -0.12504934821e-5, + 0.1133027232e-5, + -0.2056338417e-6, + 0.6116095e-8, + 0.50020075e-8, + -0.11812746e-8, + 0.1043427e-9, + 0.77823e-11, + -0.36968e-11, + 0.51e-12, + -0.206e-13, + -0.54e-14, + 0.14e-14}; + + if (x > 171.0) return FPMAX; // This value is an overflow flag. + if (x == (int)x) { + if (x > 0.0) { + ga = 1.0; // use factorial + for (i=2;i 1.0) { + z = fabs(x); + m = (int)z; + r = 1.0; + for (k=1;k<=m;k++) { + r *= (z-k); + } + z -= m; + } + else + z = x; + gr = g[24]; + for (k=23;k>=0;k--) { + gr = gr*z+g[k]; + } + ga = 1.0/(gr*z); + if (fabs(x) > 1.0) { + ga *= r; + if (x < 0.0) { + ga = -M_PI/(x*ga*sin(M_PI*x)); + } + } + } + return ga; +} + +template +int bessjy01a(P x,P &j0,P &j1,P &y0,P &y1, + P &j0p,P &j1p,P &y0p,P &y1p) +{ + const P EPS = std::numeric_limits

::epsilon(); + const P FPMIN_MAG = std::numeric_limits

::min(); + const P FPMIN = std::numeric_limits

::lowest(); + const P FPMAX = std::numeric_limits

::max(); + + P x2,r,ec,w0,w1,r0,r1,cs0,cs1; + P cu,p0,q0,p1,q1,t1,t2; + int k,kz; + static P a[] = { + -7.03125e-2, + 0.112152099609375, + -0.5725014209747314, + 6.074042001273483, + -1.100171402692467e2, + 3.038090510922384e3, + -1.188384262567832e5, + 6.252951493434797e6, + -4.259392165047669e8, + 3.646840080706556e10, + -3.833534661393944e12, + 4.854014686852901e14, + -7.286857349377656e16, + 1.279721941975975e19}; + static P b[] = { + 7.32421875e-2, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1, + 5.513358961220206e2, + -1.825775547429318e4, + 8.328593040162893e5, + -5.006958953198893e7, + 3.836255180230433e9, + -3.649010818849833e11, + 4.218971570284096e13, + -5.827244631566907e15, + 9.476288099260110e17, + -1.792162323051699e20}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947, + 1.215978918765359e2, + -3.302272294480852e3, + 1.276412726461746e5, + -6.656367718817688e6, + 4.502786003050393e8, + -3.833857520742790e10, + 4.011838599133198e12, + -5.060568503314727e14, + 7.572616461117958e16, + -1.326257285320556e19}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1, + -6.038440767050702e2, + 1.971837591223663e4, + -8.902978767070678e5, + 5.310411010968522e7, + -4.043620325107754e9, + 3.827011346598605e11, + -4.406481417852278e13, + 6.065091351222699e15, + -9.833883876590679e17, + 1.855045211579828e20}; + + if (x < 0.0) return 1; + if (x == 0.0) { + j0 = 1.0; + j1 = 0.0; + y0 = -FPMIN; + y1 = -FPMIN; + j0p = 0.0; + j1p = 0.5; + y0p = FPMAX; + y1p = FPMAX; + return 0; + } + x2 = x*x; + if (x <= 12.0) { + j0 = 1.0; + r = 1.0; + for (k=1;k<=30;k++) { + r *= -0.25*x2/(k*k); + j0 += r; + if (fabs(r) < fabs(j0)*1e-15) break; + } + j1 = 1.0; + r = 1.0; + for (k=1;k<=30;k++) { + r *= -0.25*x2/(k*(k+1)); + j1 += r; + if (fabs(r) < fabs(j1)*1e-15) break; + } + j1 *= 0.5*x; + ec = log(0.5*x)+el; + cs0 = 0.0; + w0 = 0.0; + r0 = 1.0; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + r0 *= -0.25*x2/(k*k); + r = r0 * w0; + cs0 += r; + if (fabs(r) < fabs(cs0)*1e-15) break; + } + y0 = M_2_PI*(ec*j0-cs0); + cs1 = 1.0; + w1 = 0.0; + r1 = 1.0; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + r1 *= -0.25*x2/(k*(k+1)); + r = r1*(2.0*w1+1.0/(k+1)); + cs1 += r; + if (fabs(r) < fabs(cs1)*1e-15) break; + } + y1 = M_2_PI * (ec*j1-1.0/x-0.25*x*cs1); + } + else { + if (x >= 50.0) kz = 8; // Can be changed to 10 + else if (x >= 35.0) kz = 10; // " " 12 + else kz = 12; // " " 14 + t1 = x-M_PI_4; + p0 = 1.0; + q0 = -0.125/x; + for (k=0;k +int bessjy01b(P x,P &j0,P &j1,P &y0,P &y1, + P &j0p,P &j1p,P &y0p,P &y1p) +{ + P t,t2,dtmp,a0,p0,q0,p1,q1,ta0,ta1; + if (x < 0.0) return 1; + if (x == 0.0) { + j0 = 1.0; + j1 = 0.0; + y0 = -1e308; + y1 = -1e308; + j0p = 0.0; + j1p = 0.5; + y0p = 1e308; + y1p = 1e308; + return 0; + } + if(x <= 4.0) { + t = x/4.0; + t2 = t*t; + j0 = ((((((-0.5014415e-3*t2+0.76771853e-2)*t2-0.0709253492)*t2+ + 0.4443584263)*t2-1.7777560599)*t2+3.9999973021)*t2 + -3.9999998721)*t2+1.0; + j1 = t*(((((((-0.1289769e-3*t2+0.22069155e-2)*t2-0.0236616773)*t2+ + 0.1777582922)*t2-0.8888839649)*t2+2.6666660544)*t2- + 3.999999971)*t2+1.9999999998); + dtmp = (((((((-0.567433e-4*t2+0.859977e-3)*t2-0.94855882e-2)*t2+ + 0.0772975809)*t2-0.4261737419)*t2+1.4216421221)*t2- + 2.3498519931)*t2+1.0766115157)*t2+0.3674669052; + y0 = M_2_PI*log(0.5*x)*j0+dtmp; + dtmp = (((((((0.6535773e-3*t2-0.0108175626)*t2+0.107657607)*t2- + 0.7268945577)*t2+3.1261399273)*t2-7.3980241381)*t2+ + 6.8529236342)*t2+0.3932562018)*t2-0.6366197726; + y1 = M_2_PI*log(0.5*x)*j1+dtmp/x; + } + else { + t = 4.0/x; + t2 = t*t; + a0 = sqrt(M_2_PI/x); + p0 = ((((-0.9285e-5*t2+0.43506e-4)*t2-0.122226e-3)*t2+ + 0.434725e-3)*t2-0.4394275e-2)*t2+0.999999997; + q0 = t*(((((0.8099e-5*t2-0.35614e-4)*t2+0.85844e-4)*t2- + 0.218024e-3)*t2+0.1144106e-2)*t2-0.031249995); + ta0 = x-M_PI_4; + j0 = a0*(p0*cos(ta0)-q0*sin(ta0)); + y0 = a0*(p0*sin(ta0)+q0*cos(ta0)); + p1 = ((((0.10632e-4*t2-0.50363e-4)*t2+0.145575e-3)*t2 + -0.559487e-3)*t2+0.7323931e-2)*t2+1.000000004; + q1 = t*(((((-0.9173e-5*t2+0.40658e-4)*t2-0.99941e-4)*t2 + +0.266891e-3)*t2-0.1601836e-2)*t2+0.093749994); + ta1 = x-0.75*M_PI; + j1 = a0*(p1*cos(ta1)-q1*sin(ta1)); + y1 = a0*(p1*sin(ta1)+q1*cos(ta1)); + } + j0p = -j1; + j1p = j0-j1/x; + y0p = -y1; + y1p = y0-y1/x; + return 0; +} +template +int msta1(P x,int mp) +{ + P a0,f0,f1,f; + int i,n0,n1,nn; + + a0 = fabs(x); + n0 = (int)(1.1*a0)+1; + f0 = 0.5*log10(6.28*n0)-n0*log10(1.36*a0/n0)-mp; + n1 = n0+5; + f1 = 0.5*log10(6.28*n1)-n1*log10(1.36*a0/n1)-mp; + for (i=0;i<20;i++) { + nn = (int)(n1-(n1-n0)/(1.0-f0/f1)); + f = 0.5*log10(6.28*nn)-nn*log10(1.36*a0/nn)-mp; + if (std::abs(nn-n1) < 1) break; + n0 = n1; + f0 = f1; + n1 = nn; + f1 = f; + } + return nn; +} +template +int msta2(P x,int n,int mp) +{ + P a0,ejn,hmp,f0,f1,f,obj; + int i,n0,n1,nn; + + a0 = fabs(x); + hmp = 0.5*mp; + ejn = 0.5*log10(6.28*n)-n*log10(1.36*a0/n); + if (ejn <= hmp) { + obj = mp; + n0 = (int)(1.1*a0); + if (n0 < 1) n0 = 1; + } + else { + obj = hmp+ejn; + n0 = n; + } + f0 = 0.5*log10(6.28*n0)-n0*log10(1.36*a0/n0)-obj; + n1 = n0+5; + f1 = 0.5*log10(6.28*n1)-n1*log10(1.36*a0/n1)-obj; + for (i=0;i<20;i++) { + nn = (int)(n1-(n1-n0)/(1.0-f0/f1)); + f = 0.5*log10(6.28*nn)-nn*log10(1.36*a0/nn)-obj; + if (std::abs(nn-n1) < 1) break; + n0 = n1; + f0 = f1; + n1 = nn; + f1 = f; + } + return nn+10; +} +// +// INPUT: +// double x -- argument of Bessel function of 1st and 2nd kind. +// int n -- order +// +// OUPUT: +// +// int nm -- highest order actually computed (nm <= n) +// double jn[] -- Bessel function of 1st kind, orders from 0 to nm +// double yn[] -- Bessel function of 2nd kind, orders from 0 to nm +// double j'n[]-- derivative of Bessel function of 1st kind, +// orders from 0 to nm +// double y'n[]-- derivative of Bessel function of 2nd kind, +// orders from 0 to nm +// +// Computes Bessel functions of all order up to 'n' using recurrence +// relations. If 'nm' < 'n' only 'nm' orders are returned. +// +template +int bessjyna(int n,P x,int &nm,P *jn,P *yn, + P *jnp,P *ynp) +{ + P bj0,bj1,f,f0,f1,f2,cs; + int i,k,m,ecode; + + nm = n; + if ((x < 0.0) || (n < 0)) return 1; + if (x < 1e-15) { + for (i=0;i<=n;i++) { + jn[i] = 0.0; + yn[i] = -1e308; + jnp[i] = 0.0; + ynp[i] = 1e308; + } + jn[0] = 1.0; + jnp[1] = 0.5; + return 0; + } + ecode = bessjy01a(x,jn[0],jn[1],yn[0],yn[1],jnp[0],jnp[1],ynp[0],ynp[1]); + if (n < 2) return 0; + bj0 = jn[0]; + bj1 = jn[1]; + if (n < (int)0.9*x) { + for (k=2;k<=n;k++) { + jn[k] = 2.0*(k-1.0)*bj1/x-bj0; + bj0 = bj1; + bj1 = jn[k]; + } + } + else { + m = msta1(x,200); + if (m < n) nm = m; + else m = msta2(x,n,15); + f2 = 0.0; + f1 = 1.0e-100; + for (k=m;k>=0;k--) { + f = 2.0*(k+1.0)/x*f1-f2; + if (k <= nm) jn[k] = f; + f2 = f1; + f1 = f; + } + if (fabs(bj0) > fabs(bj1)) cs = bj0/f; + else cs = bj1/f2; + for (k=0;k<=nm;k++) { + jn[k] *= cs; + } + } + for (k=2;k<=nm;k++) { + jnp[k] = jn[k-1]-k*jn[k]/x; + } + f0 = yn[0]; + f1 = yn[1]; + for (k=2;k<=nm;k++) { + f = 2.0*(k-1.0)*f1/x-f0; + yn[k] = f; + f0 = f1; + f1 = f; + } + for (k=2;k<=nm;k++) { + ynp[k] = yn[k-1]-k*yn[k]/x; + } + return 0; +} +// +// Same input and output conventions as above. Different recurrence +// relations used for 'x' < 300. +// +template +int bessjynb(int n,P x,int &nm,P *jn,P *yn, + P *jnp,P *ynp) +{ + P t1,t2,f,f1,f2,bj0,bj1,bjk,by0,by1,cu,s0,su,sv; + P ec,bs,byk,p0,p1,q0,q1; + static P a[] = { + -0.7031250000000000e-1, + 0.1121520996093750, + -0.5725014209747314, + 6.074042001273483}; + static P b[] = { + 0.7324218750000000e-1, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1}; + + int i,k,m; + nm = n; + if ((x < 0.0) || (n < 0)) return 1; + if (x < 1e-15) { + for (i=0;i<=n;i++) { + jn[i] = 0.0; + yn[i] = -1e308; + jnp[i] = 0.0; + ynp[i] = 1e308; + } + jn[0] = 1.0; + jnp[1] = 0.5; + return 0; + } + if (x <= 300.0 || n > (int)(0.9*x)) { + if (n == 0) nm = 1; + m = msta1(x,200); + if (m < nm) nm = m; + else m = msta2(x,nm,15); + bs = 0.0; + su = 0.0; + sv = 0.0; + f2 = 0.0; + f1 = 1.0e-100; + for (k = m;k>=0;k--) { + f = 2.0*(k+1.0)/x*f1 - f2; + if (k <= nm) jn[k] = f; + if ((k == 2*(int)(k/2)) && (k != 0)) { + bs += 2.0*f; +// su += pow(-1,k>>1)*f/(double)k; + su += (-1)*((k & 2)-1)*f/(P)k; + } + else if (k > 1) { +// sv += pow(-1,k>>1)*k*f/(k*k-1.0); + sv += (-1)*((k & 2)-1)*(P)k*f/(k*k-1.0); + } + f2 = f1; + f1 = f; + } + s0 = bs+f; + for (k=0;k<=nm;k++) { + jn[k] /= s0; + } + ec = log(0.5*x) +0.5772156649015329; + by0 = M_2_PI*(ec*jn[0]-4.0*su/s0); + yn[0] = by0; + by1 = M_2_PI*((ec-1.0)*jn[1]-jn[0]/x-4.0*sv/s0); + yn[1] = by1; + } + else { + t1 = x-M_PI_4; + p0 = 1.0; + q0 = -0.125/x; + for (k=0;k<4;k++) { + p0 += a[k]*pow(x,-2*k-2); + q0 += b[k]*pow(x,-2*k-3); + } + cu = sqrt(M_2_PI/x); + bj0 = cu*(p0*cos(t1)-q0*sin(t1)); + by0 = cu*(p0*sin(t1)+q0*cos(t1)); + jn[0] = bj0; + yn[0] = by0; + t2 = x-0.75*M_PI; + p1 = 1.0; + q1 = 0.375/x; + for (k=0;k<4;k++) { + p1 += a1[k]*pow(x,-2*k-2); + q1 += b1[k]*pow(x,-2*k-3); + } + bj1 = cu*(p1*cos(t2)-q1*sin(t2)); + by1 = cu*(p1*sin(t2)+q1*cos(t2)); + jn[1] = bj1; + yn[1] = by1; + for (k=2;k<=nm;k++) { + bjk = 2.0*(k-1.0)*bj1/x-bj0; + jn[k] = bjk; + bj0 = bj1; + bj1 = bjk; + } + } + jnp[0] = -jn[1]; + for (k=1;k<=nm;k++) { + jnp[k] = jn[k-1]-k*jn[k]/x; + } + for (k=2;k<=nm;k++) { + byk = 2.0*(k-1.0)*by1/x-by0; + yn[k] = byk; + by0 = by1; + by1 = byk; + } + ynp[0] = -yn[1]; + for (k=1;k<=nm;k++) { + ynp[k] = yn[k-1]-k*yn[k]/x; + } + return 0; + +} + +// The following routine computes Bessel Jv(x) and Yv(x) for +// arbitrary positive order (v). For negative order, use: +// +// J-v(x) = Jv(x)cos(v pi) - Yv(x)sin(v pi) +// Y-v(x) = Jv(x)sin(v pi) + Yv(x)cos(v pi) +// +template +int bessjyv(P v,P x,P &vm,P *jv,P *yv, + P *djv,P *dyv) +{ + P v0,vl,vg,vv,a,a0,r,x2,bjv0,bjv1,bjvl,f,f0,f1,f2; + P r0,r1,ck,cs,cs0,cs1,sk,qx,px,byv0,byv1,rp,xk,rq; + P b,ec,w0,w1,bju0,bju1,pv0,pv1,byvk; + int j,k,l,m,n,kz; + + const P EPS = std::numeric_limits

::epsilon(); + const P FPMIN_MAG = std::numeric_limits

::min(); + const P FPMIN = std::numeric_limits

::lowest(); + const P FPMAX = std::numeric_limits

::max(); + + x2 = x*x; + n = (int)v; + v0 = v-n; + if ((x < 0.0) || (v < 0.0)) return 1; + if (x < EPS) { + for (k=0;k<=n;k++) { + jv[k] = 0.0; + yv[k] = FPMIN; + djv[k] = 0.0; + dyv[k] = FPMAX; + if (v0 == 0.0) { + jv[0] = 1.0; + djv[1] = 0.5; + } + else djv[0] = FPMAX; + } + vm = v; + return 0; + } + if (x <= 12.0) { + for (l=0;l<2;l++) { + vl = v0 + l; + bjvl = 1.0; + r = 1.0; + for (k=1;k<=40;k++) { + r *= -0.25*x2/(k*(k+vl)); + bjvl += r; + if (fabs(r) < fabs(bjvl)*EPS) break; + } + vg = 1.0 + vl; + a = pow(0.5*x,vl)/gamma(vg); + if (l == 0) bjv0 = bjvl*a; + else bjv1 = bjvl*a; + } + } + else { + if (x >= 50.0) kz = 8; + else if (x >= 35.0) kz = 10; + else kz = 11; + for (j=0;j<2;j++) { + vv = 4.0*(j+v0)*(j+v0); + px = 1.0; + rp = 1.0; + for (k=1;k<=kz;k++) { + rp *= (-0.78125e-2)*(vv-pow(4.0*k-3.0,2.0))* + (vv-pow(4.0*k-1.0,2.0))/(k*(2.0*k-1.0)*x2); + px += rp; + } + qx = 1.0; + rq = 1.0; + for (k=1;k<=kz;k++) { + rq *= (-0.78125e-2)*(vv-pow(4.0*k-1.0,2.0))* + (vv-pow(4.0*k+1.0,2.0))/(k*(2.0*k+1.0)*x2); + qx += rq; + } + qx *= 0.125*(vv-1.0)/x; + xk = x-(0.5*(j+v0)+0.25)*M_PI; + a0 = sqrt(M_2_PI/x); + ck = cos(xk); + sk = sin(xk); + + if (j == 0) { + bjv0 = a0*(px*ck-qx*sk); + byv0 = a0*(px*sk+qx*ck); + } + else if (j == 1) { + bjv1 = a0*(px*ck-qx*sk); + byv1 = a0*(px*sk+qx*ck); + } + } + } + jv[0] = bjv0; + jv[1] = bjv1; + djv[0] = v0*jv[0]/x-jv[1]; + djv[1] = -(1.0+v0)*jv[1]/x+jv[0]; + if ((n >= 2) && (n <= (int)(0.9*x))) { + f0 = bjv0; + f1 = bjv1; + for (k=2;k<=n;k++) { + f = 2.0*(k+v0-1.0)*f1/x-f0; + jv[k] = f; + f0 = f1; + f1 = f; + } + } + else if (n >= 2) { + m = msta1(x,200); + if (m < n) n = m; + else m = msta2(x,n,15); + f2 = 0.0; + f1 = FPMIN_MAG; + for (k=m;k>=0;k--) { + f = 2.0*(v0+k+1.0)*f1/x-f2; + if (k <= n) jv[k] = f; + f2 = f1; + f1 = f; + } + if (fabs(bjv0) > fabs(bjv1)) cs = bjv0/f; + else cs = bjv1/f2; + for (k=0;k<=n;k++) { + jv[k] *= cs; + } + } + for (k=2;k<=n;k++) { + djv[k] = -(k+v0)*jv[k]/x+jv[k-1]; + } + if (x <= 12.0) { + if (v0 != 0.0) { + for (l=0;l<2;l++) { + vl = v0 +l; + bjvl = 1.0; + r = 1.0; + for (k=1;k<=40;k++) { + r *= -0.25*x2/(k*(k-vl)); + bjvl += r; + if (fabs(r) < fabs(bjvl)*1e-15) break; + } + vg = 1.0-vl; + b = pow(2.0/x,vl)/gamma(vg); + if (l == 0) bju0 = bjvl*b; + else bju1 = bjvl*b; + } + pv0 = M_PI*v0; + pv1 = M_PI*(1.0+v0); + byv0 = (bjv0*cos(pv0)-bju0)/sin(pv0); + byv1 = (bjv1*cos(pv1)-bju1)/sin(pv1); + } + else { + ec = log(0.5*x)+el; + cs0 = 0.0; + w0 = 0.0; + r0 = 1.0; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + r0 *= -0.25*x2/(k*k); + cs0 += r0*w0; + } + byv0 = M_2_PI*(ec*bjv0-cs0); + cs1 = 1.0; + w1 = 0.0; + r1 = 1.0; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + r1 *= -0.25*x2/(k*(k+1)); + cs1 += r1*(2.0*w1+1.0/(k+1.0)); + } + byv1 = M_2_PI*(ec*bjv1-1.0/x-0.25*x*cs1); + } + } + yv[0] = byv0; + yv[1] = byv1; + for (k=2;k<=n;k++) { + byvk = 2.0*(v0+k-1.0)*byv1/x-byv0; + yv[k] = byvk; + byv0 = byv1; + byv1 = byvk; + } + dyv[0] = v0*yv[0]/x-yv[1]; + for (k=1;k<=n;k++) { + dyv[k] = -(k+v0)*yv[k]/x+yv[k-1]; + } + vm = n + v0; + return 0; +} + +template +int bessjyv_sph(int v, P z, P &vm, P* cjv, + P* cyv, P* cjvp, P* cyvp){ + + //first, compute the bessel functions of fractional order + bessjyv

(v + (P)0.5, z, vm, cjv, cyv, cjvp, cyvp); + + if(z == 0){ //handle degenerate case of z = 0 + memset(cjv, 0, sizeof(P) * (v+1)); + cjv[0] = 1; + } + + //iterate through each and scale + for(int n = 0; n<=v; n++){ + + if(z != 0){ //handle degenerate case of z = 0 + cjv[n] = cjv[n] * sqrt(stim::PI/(z * 2.0)); + cyv[n] = cyv[n] * sqrt(stim::PI/(z * 2.0)); + } + + cjvp[n] = -1.0 / (z * 2.0) * cjv[n] + cjvp[n] * sqrt(stim::PI / (z * 2.0)); + cyvp[n] = -1.0 / (z * 2.0) * cyv[n] + cyvp[n] * sqrt(stim::PI / (z * 2.0)); + } + + return 0; + +} + +template +int cbessjy01(complex

z,complex

&cj0,complex

&cj1, + complex

&cy0,complex

&cy1,complex

&cj0p, + complex

&cj1p,complex

&cy0p,complex

&cy1p) +{ + complex

z1,z2,cr,cp,cs,cp0,cq0,cp1,cq1,ct1,ct2,cu; + P a0,w0,w1; + int k,kz; + + static P a[] = { + -7.03125e-2, + 0.112152099609375, + -0.5725014209747314, + 6.074042001273483, + -1.100171402692467e2, + 3.038090510922384e3, + -1.188384262567832e5, + 6.252951493434797e6, + -4.259392165047669e8, + 3.646840080706556e10, + -3.833534661393944e12, + 4.854014686852901e14, + -7.286857349377656e16, + 1.279721941975975e19}; + static P b[] = { + 7.32421875e-2, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1, + 5.513358961220206e2, + -1.825775547429318e4, + 8.328593040162893e5, + -5.006958953198893e7, + 3.836255180230433e9, + -3.649010818849833e11, + 4.218971570284096e13, + -5.827244631566907e15, + 9.476288099260110e17, + -1.792162323051699e20}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947, + 1.215978918765359e2, + -3.302272294480852e3, + 1.276412726461746e5, + -6.656367718817688e6, + 4.502786003050393e8, + -3.833857520742790e10, + 4.011838599133198e12, + -5.060568503314727e14, + 7.572616461117958e16, + -1.326257285320556e19}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1, + -6.038440767050702e2, + 1.971837591223663e4, + -8.902978767070678e5, + 5.310411010968522e7, + -4.043620325107754e9, + 3.827011346598605e11, + -4.406481417852278e13, + 6.065091351222699e15, + -9.833883876590679e17, + 1.855045211579828e20}; + + a0 = abs(z); + z2 = z*z; + z1 = z; + if (a0 == 0.0) { + cj0 = cone; + cj1 = czero; + cy0 = complex

(-1e308,0); + cy1 = complex

(-1e308,0); + cj0p = czero; + cj1p = complex

(0.5,0.0); + cy0p = complex

(1e308,0); + cy1p = complex

(1e308,0); + return 0; + } + if (real(z) < 0.0) z1 = -z; + if (a0 <= 12.0) { + cj0 = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(P)(k*k); + cj0 += cr; + if (abs(cr) < abs(cj0)*eps) break; + } + cj1 = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k+1.0)); + cj1 += cr; + if (abs(cr) < abs(cj1)*eps) break; + } + cj1 *= 0.5*z1; + w0 = 0.0; + cr = cone; + cs = czero; + for (k=1;k<=40;k++) { + w0 += 1.0/k; + cr *= -0.25*z2/(P)(k*k); + cp = cr*w0; + cs += cp; + if (abs(cp) < abs(cs)*eps) break; + } + cy0 = M_2_PI*((log(0.5*z1)+el)*cj0-cs); + w1 = 0.0; + cr = cone; + cs = cone; + for (k=1;k<=40;k++) { + w1 += 1.0/k; + cr *= -0.25*z2/(k*(k+1.0)); + cp = cr*(2.0*w1+1.0/(k+1.0)); + cs += cp; + if (abs(cp) < abs(cs)*eps) break; + } + cy1 = M_2_PI*((log(0.5*z1)+el)*cj1-1.0/z1-0.25*z1*cs); + } + else { + if (a0 >= 50.0) kz = 8; // can be changed to 10 + else if (a0 >= 35.0) kz = 10; // " " " 12 + else kz = 12; // " " " 14 + ct1 = z1 - M_PI_4; + cp0 = cone; + for (k=0;k 0.0) { + cy0 += 2.0*cii*cj0; + cy1 = -(cy1+2.0*cii*cj1); + } + cj1 = -cj1; + } + cj0p = -cj1; + cj1p = cj0-cj1/z; + cy0p = -cy1; + cy1p = cy0-cy1/z; + return 0; +} + +template +int cbessjyna(int n,complex

z,int &nm,complex

*cj, + complex

*cy,complex

*cjp,complex

*cyp) +{ + complex

cbj0,cbj1,cby0,cby1,cj0,cjk,cj1,cf,cf1,cf2; + complex

cs,cg0,cg1,cyk,cyl1,cyl2,cylk,cp11,cp12,cp21,cp22; + complex

ch0,ch1,ch2; + P a0,yak,ya1,ya0,wa; + int m,k,lb,lb0; + + if (n < 0) return 1; + a0 = abs(z); + nm = n; + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cj[k] = czero; + cy[k] = complex

(-1e308,0); + cjp[k] = czero; + cyp[k] = complex

(1e308,0); + } + cj[0] = cone; + cjp[1] = complex

(0.5,0.0); + return 0; + } + cbessjy01(z,cj[0],cj[1],cy[0],cy[1],cjp[0],cjp[1],cyp[0],cyp[1]); + cbj0 = cj[0]; + cbj1 = cj[1]; + cby0 = cy[0]; + cby1 = cy[1]; + if (n <= 1) return 0; + if (n < (int)0.25*a0) { + cj0 = cbj0; + cj1 = cbj1; + for (k=2;k<=n;k++) { + cjk = 2.0*(k-1.0)*cj1/z-cj0; + cj[k] = cjk; + cj0 = cj1; + cj1 = cjk; + } + } + else { + m = msta1(a0,200); + if (m < n) nm = m; + else m = msta2(a0,n,15); + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(k+1.0)*cf1/z-cf2; + if (k <=nm) cj[k] = cf; + cf2 = cf1; + cf1 = cf; + } + if (abs(cbj0) > abs(cbj1)) cs = cbj0/cf; + else cs = cbj1/cf2; + for (k=0;k<=nm;k++) { + cj[k] *= cs; + } + } + for (k=2;k<=nm;k++) { + cjp[k] = cj[k-1]-(P)k*cj[k]/z; + } + ya0 = abs(cby0); + lb = 0; + cg0 = cby0; + cg1 = cby1; + for (k=2;k<=nm;k++) { + cyk = 2.0*(k-1.0)*cg1/z-cg0; + yak = abs(cyk); + ya1 = abs(cg0); + if ((yak < ya0) && (yak < ya1)) lb = k; + cy[k] = cyk; + cg0 = cg1; + cg1 = cyk; + } + lb0 = 0; + if ((lb > 4) && (imag(z) != 0.0)) { + while (lb != lb0) { + ch2 = cone; + ch1 = czero; + lb0 = lb; + for (k=lb;k>=1;k--) { + ch0 = 2.0*k*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp12 = ch0; + cp22 = ch2; + ch2 = czero; + ch1 = cone; + for (k=lb;k>=1;k--) { + ch0 = 2.0*k*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp11 = ch0; + cp21 = ch2; + if (lb == nm) + cj[lb+1] = 2.0*lb*cj[lb]/z-cj[lb-1]; + if (abs(cj[0]) > abs(cj[1])) { + cy[lb+1] = (cj[lb+1]*cby0-2.0*cp11/(M_PI*z))/cj[0]; + cy[lb] = (cj[lb]*cby0+2.0*cp12/(M_PI*z))/cj[0]; + } + else { + cy[lb+1] = (cj[lb+1]*cby1-2.0*cp21/(M_PI*z))/cj[1]; + cy[lb] = (cj[lb]*cby1+2.0*cp22/(M_PI*z))/cj[1]; + } + cyl2 = cy[lb+1]; + cyl1 = cy[lb]; + for (k=lb-1;k>=0;k--) { + cylk = 2.0*(k+1.0)*cyl1/z-cyl2; + cy[k] = cylk; + cyl2 = cyl1; + cyl1 = cylk; + } + cyl1 = cy[lb]; + cyl2 = cy[lb+1]; + for (k=lb+1;k +int cbessjynb(int n,complex

z,int &nm,complex

*cj, + complex

*cy,complex

*cjp,complex

*cyp) +{ + complex

cf,cf0,cf1,cf2,cbs,csu,csv,cs0,ce; + complex

ct1,cp0,cq0,cp1,cq1,cu,cbj0,cby0,cbj1,cby1; + complex

cyy,cbjk,ct2; + P a0,y0; + int k,m; + static P a[] = { + -0.7031250000000000e-1, + 0.1121520996093750, + -0.5725014209747314, + 6.074042001273483}; + static P b[] = { + 0.7324218750000000e-1, + -0.2271080017089844, + 1.727727502584457, + -2.438052969955606e1}; + static P a1[] = { + 0.1171875, + -0.1441955566406250, + 0.6765925884246826, + -6.883914268109947}; + static P b1[] = { + -0.1025390625, + 0.2775764465332031, + -1.993531733751297, + 2.724882731126854e1}; + + y0 = abs(imag(z)); + a0 = abs(z); + nm = n; + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cj[k] = czero; + cy[k] = complex

(-1e308,0); + cjp[k] = czero; + cyp[k] = complex

(1e308,0); + } + cj[0] = cone; + cjp[1] = complex

(0.5,0.0); + return 0; + } + if ((a0 <= 300.0) || (n > (int)(0.25*a0))) { + if (n == 0) nm = 1; + m = msta1(a0,200); + if (m < nm) nm = m; + else m = msta2(a0,nm,15); + cbs = czero; + csu = czero; + csv = czero; + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(k+1.0)*cf1/z-cf2; + if (k <= nm) cj[k] = cf; + if (((k & 1) == 0) && (k != 0)) { + if (y0 <= 1.0) { + cbs += 2.0*cf; + } + else { + cbs += (-1)*((k & 2)-1)*2.0*cf; + } + csu += (P)((-1)*((k & 2)-1))*cf/(P)k; + } + else if (k > 1) { + csv += (P)((-1)*((k & 2)-1)*k)*cf/(P)(k*k-1.0); + } + cf2 = cf1; + cf1 = cf; + } + if (y0 <= 1.0) cs0 = cbs+cf; + else cs0 = (cbs+cf)/cos(z); + for (k=0;k<=nm;k++) { + cj[k] /= cs0; + } + ce = log(0.5*z)+el; + cy[0] = M_2_PI*(ce*cj[0]-4.0*csu/cs0); + cy[1] = M_2_PI*(-cj[0]/z+(ce-1.0)*cj[1]-4.0*csv/cs0); + } + else { + ct1 = z-M_PI_4; + cp0 = cone; + for (k=0;k<4;k++) { + cp0 += a[k]*pow(z,-2.0*k-2.0); + } + cq0 = -0.125/z; + for (k=0;k<4;k++) { + cq0 += b[k] *pow(z,-2.0*k-3.0); + } + cu = sqrt(M_2_PI/z); + cbj0 = cu*(cp0*cos(ct1)-cq0*sin(ct1)); + cby0 = cu*(cp0*sin(ct1)+cq0*cos(ct1)); + cj[0] = cbj0; + cy[0] = cby0; + ct2 = z-0.75*M_PI; + cp1 = cone; + for (k=0;k<4;k++) { + cp1 += a1[k]*pow(z,-2.0*k-2.0); + } + cq1 = 0.375/z; + for (k=0;k<4;k++) { + cq1 += b1[k]*pow(z,-2.0*k-3.0); + } + cbj1 = cu*(cp1*cos(ct2)-cq1*sin(ct2)); + cby1 = cu*(cp1*sin(ct2)+cq1*cos(ct2)); + cj[1] = cbj1; + cy[1] = cby1; + for (k=2;k<=n;k++) { + cbjk = 2.0*(k-1.0)*cbj1/z-cbj0; + cj[k] = cbjk; + cbj0 = cbj1; + cbj1 = cbjk; + } + } + cjp[0] = -cj[1]; + for (k=1;k<=nm;k++) { + cjp[k] = cj[k-1]-(P)k*cj[k]/z; + } + if (abs(cj[0]) > 1.0) + cy[1] = (cj[1]*cy[0]-2.0/(M_PI*z))/cj[0]; + for (k=2;k<=nm;k++) { + if (abs(cj[k-1]) >= abs(cj[k-2])) + cyy = (cj[k]*cy[k-1]-2.0/(M_PI*z))/cj[k-1]; + else + cyy = (cj[k]*cy[k-2]-4.0*(k-1.0)/(M_PI*z*z))/cj[k-2]; + cy[k] = cyy; + } + cyp[0] = -cy[1]; + for (k=1;k<=nm;k++) { + cyp[k] = cy[k-1]-(P)k*cy[k]/z; + } + + return 0; +} + +template +int cbessjyva(P v,complex

z,P &vm,complex

*cjv, + complex

*cyv,complex

*cjvp,complex

*cyvp) +{ + complex

z1,z2,zk,cjvl,cr,ca,cjv0,cjv1,cpz,crp; + complex

cqz,crq,ca0,cck,csk,cyv0,cyv1,cju0,cju1,cb; + complex

cs,cs0,cr0,cs1,cr1,cec,cf,cf0,cf1,cf2; + complex

cfac0,cfac1,cg0,cg1,cyk,cp11,cp12,cp21,cp22; + complex

ch0,ch1,ch2,cyl1,cyl2,cylk; + + P a0,v0,pv0,pv1,vl,ga,gb,vg,vv,w0,w1,ya0,yak,ya1,wa; + int j,n,k,kz,l,lb,lb0,m; + + a0 = ::abs(z); + z1 = z; + z2 = z*z; + n = (int)v; + + + v0 = v-n; + + pv0 = M_PI*v0; + pv1 = M_PI*(1.0+v0); + if (a0 < 1.0e-100) { + for (k=0;k<=n;k++) { + cjv[k] = czero; + cyv[k] = complex

(-1e308,0); + cjvp[k] = czero; + cyvp[k] = complex

(1e308,0); + + } + if (v0 == 0.0) { + cjv[0] = cone; + cjvp[1] = complex

(0.5,0.0); + } + else { + cjvp[0] = complex

(1e308,0); + } + vm = v; + return 0; + } + if (::real(z1) < 0.0) z1 = -z; + if (a0 <= 12.0) { + for (l=0;l<2;l++) { + vl = v0+l; + cjvl = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k+vl)); + cjvl += cr; + if (::abs(cr) < ::abs(cjvl)*eps) break; + } + vg = 1.0 + vl; + ga = gamma(vg); + ca = pow(0.5*z1,vl)/ga; + if (l == 0) cjv0 = cjvl*ca; + else cjv1 = cjvl*ca; + } + } + else { + if (a0 >= 50.0) kz = 8; + else if (a0 >= 35.0) kz = 10; + else kz = 11; + for (j=0;j<2;j++) { + vv = 4.0*(j+v0)*(j+v0); + cpz = cone; + crp = cone; + for (k=1;k<=kz;k++) { + crp = -0.78125e-2*crp*(vv-pow(4.0*k-3.0,2.0))* + (vv-pow(4.0*k-1.0,2.0))/(k*(2.0*k-1.0)*z2); + cpz += crp; + } + cqz = cone; + crq = cone; + for (k=1;k<=kz;k++) { + crq = -0.78125e-2*crq*(vv-pow(4.0*k-1.0,2.0))* + (vv-pow(4.0*k+1.0,2.0))/(k*(2.0*k+1.0)*z2); + cqz += crq; + } + cqz *= 0.125*(vv-1.0)/z1; + zk = z1-(0.5*(j+v0)+0.25)*M_PI; + ca0 = sqrt(M_2_PI/z1); + cck = cos(zk); + csk = sin(zk); + if (j == 0) { + cjv0 = ca0*(cpz*cck-cqz*csk); + cyv0 = ca0*(cpz*csk+cqz+cck); + } + else { + cjv1 = ca0*(cpz*cck-cqz*csk); + cyv1 = ca0*(cpz*csk+cqz*cck); + } + } + } + if (a0 <= 12.0) { + if (v0 != 0.0) { + for (l=0;l<2;l++) { + vl = v0+l; + cjvl = cone; + cr = cone; + for (k=1;k<=40;k++) { + cr *= -0.25*z2/(k*(k-vl)); + cjvl += cr; + if (::abs(cr) < ::abs(cjvl)*eps) break; + } + vg = 1.0-vl; + gb = gamma(vg); + cb = pow(2.0/z1,vl)/gb; + if (l == 0) cju0 = cjvl*cb; + else cju1 = cjvl*cb; + } + cyv0 = (cjv0*cos(pv0)-cju0)/sin(pv0); + cyv1 = (cjv1*cos(pv1)-cju1)/sin(pv1); + } + else { + cec = log(0.5*z1)+el; + cs0 = czero; + w0 = 0.0; + cr0 = cone; + for (k=1;k<=30;k++) { + w0 += 1.0/k; + cr0 *= -0.25*z2/(P)(k*k); + cs0 += cr0*w0; + } + cyv0 = M_2_PI*(cec*cjv0-cs0); + cs1 = cone; + w1 = 0.0; + cr1 = cone; + for (k=1;k<=30;k++) { + w1 += 1.0/k; + cr1 *= -0.25*z2/(k*(k+1.0)); + cs1 += cr1*(2.0*w1+1.0/(k+1.0)); + } + cyv1 = M_2_PI*(cec*cjv1-1.0/z1-0.25*z1*cs1); + } + } + if (::real(z) < 0.0) { + cfac0 = exp(pv0*cii); + cfac1 = exp(pv1*cii); + if (::imag(z) < 0.0) { + cyv0 = cfac0*cyv0-(P)2.0*(complex

)cii*cos(pv0)*cjv0; + cyv1 = cfac1*cyv1-(P)2.0*(complex

)cii*cos(pv1)*cjv1; + cjv0 /= cfac0; + cjv1 /= cfac1; + } + else if (::imag(z) > 0.0) { + cyv0 = cyv0/cfac0+(P)2.0*(complex

)cii*cos(pv0)*cjv0; + cyv1 = cyv1/cfac1+(P)2.0*(complex

)cii*cos(pv1)*cjv1; + cjv0 *= cfac0; + cjv1 *= cfac1; + } + } + cjv[0] = cjv0; + cjv[1] = cjv1; + if ((n >= 2) && (n <= (int)(0.25*a0))) { + cf0 = cjv0; + cf1 = cjv1; + for (k=2;k<= n;k++) { + cf = 2.0*(k+v0-1.0)*cf1/z-cf0; + cjv[k] = cf; + cf0 = cf1; + cf1 = cf; + } + } + else if (n >= 2) { + m = msta1(a0,200); + if (m < n) n = m; + else m = msta2(a0,n,15); + cf2 = czero; + cf1 = complex

(1.0e-100,0.0); + for (k=m;k>=0;k--) { + cf = 2.0*(v0+k+1.0)*cf1/z-cf2; + if (k <= n) cjv[k] = cf; + cf2 = cf1; + cf1 = cf; + } + if (::abs(cjv0) > ::abs(cjv1)) cs = cjv0/cf; + else cs = cjv1/cf2; + for (k=0;k<=n;k++) { + cjv[k] *= cs; + } + } + cjvp[0] = v0*cjv[0]/z-cjv[1]; + for (k=1;k<=n;k++) { + cjvp[k] = -(k+v0)*cjv[k]/z+cjv[k-1]; + } + cyv[0] = cyv0; + cyv[1] = cyv1; + ya0 = ::abs(cyv0); + lb = 0; + cg0 = cyv0; + cg1 = cyv1; + for (k=2;k<=n;k++) { + cyk = 2.0*(v0+k-1.0)*cg1/z-cg0; + yak = ::abs(cyk); + ya1 = ::abs(cg0); + if ((yak < ya0) && (yak< ya1)) lb = k; + cyv[k] = cyk; + cg0 = cg1; + cg1 = cyk; + } + lb0 = 0; + if ((lb > 4) && (::imag(z) != 0.0)) { + while(lb != lb0) { + ch2 = cone; + ch1 = czero; + lb0 = lb; + for (k=lb;k>=1;k--) { + ch0 = 2.0*(k+v0)*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp12 = ch0; + cp22 = ch2; + ch2 = czero; + ch1 = cone; + for (k=lb;k>=1;k--) { + ch0 = 2.0*(k+v0)*ch1/z-ch2; + ch2 = ch1; + ch1 = ch0; + } + cp11 = ch0; + cp21 = ch2; + if (lb == n) + cjv[lb+1] = 2.0*(lb+v0)*cjv[lb]/z-cjv[lb-1]; + if (::abs(cjv[0]) > ::abs(cjv[1])) { + cyv[lb+1] = (cjv[lb+1]*cyv0-2.0*cp11/(M_PI*z))/cjv[0]; + cyv[lb] = (cjv[lb]*cyv0+2.0*cp12/(M_PI*z))/cjv[0]; + } + else { + cyv[lb+1] = (cjv[lb+1]*cyv1-2.0*cp21/(M_PI*z))/cjv[1]; + cyv[lb] = (cjv[lb]*cyv1+2.0*cp22/(M_PI*z))/cjv[1]; + } + cyl2 = cyv[lb+1]; + cyl1 = cyv[lb]; + for (k=lb-1;k>=0;k--) { + cylk = 2.0*(k+v0+1.0)*cyl1/z-cyl2; + cyv[k] = cylk; + cyl2 = cyl1; + cyl1 = cylk; + } + cyl1 = cyv[lb]; + cyl2 = cyv[lb+1]; + for (k=lb+1;k +int cbessjyva_sph(int v,complex

z,P &vm,complex

*cjv, + complex

*cyv,complex

*cjvp,complex

*cyvp) +{ + //first, compute the bessel functions of fractional order + cbessjyva

(v + 0.5, z, vm, cjv, cyv, cjvp, cyvp); + + if(z == 0){ //handle degenerate case of z = 0 + memset(cjv, 0, sizeof(P) * (v+1)); + cjv[0] = 1; + } + + //iterate through each and scale + for(int n = 0; n<=v; n++) + { + if(z != 0){ //handle degenerate case of z = 0 + cjv[n] = cjv[n] * sqrt(stim::PI/(z * 2.0)); + cyv[n] = cyv[n] * sqrt(stim::PI/(z * 2.0)); + } + + cjvp[n] = -1.0 / (z * 2.0) * cjv[n] + cjvp[n] * sqrt(stim::PI / (z * 2.0)); + cyvp[n] = -1.0 / (z * 2.0) * cyv[n] + cyvp[n] * sqrt(stim::PI / (z * 2.0)); + } + + return 0; + +} + +} //end namespace rts + + +#endif diff --git a/tira/math/circle.h b/tira/math/circle.h new file mode 100644 index 0000000..23b5c1a --- /dev/null +++ b/tira/math/circle.h @@ -0,0 +1,221 @@ +#ifndef STIM_CIRCLE_H +#define STIM_CIRCLE_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace stim{ + +template +class circle : plane +{ + +private: + + //stim::vec3 Y; + T R; //radius of the circle + + /*CUDA_CALLABLE void + init() + { + Y = U.cross(N).norm(); + }*/ + +public: + using stim::plane::n; + using stim::plane::P; + using stim::plane::N; + using stim::plane::U; + using stim::plane::rotate; + using stim::plane::setU; + + using stim::plane::init; + + ///base constructor + ///@param th value of the angle of the starting point from 0 to 360. + CUDA_CALLABLE + circle() : plane() + { + init(); + } + + ///create a circle given a size and position in Z space. + ///@param size: size of the rectangle in ND space. + ///@param z_pos z coordinate of the rectangle. + CUDA_CALLABLE + circle(T radius, T z_pos = (T)0) : plane(z_pos) + { + //center(stim::vec3(0, 0, z_pos)); + //scale(size); + //init(); + R = radius; + } + + ///create a rectangle from a center point, normal + ///@param c: x,y,z location of the center. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE + circle(vec3 c, vec3 n = vec3(0,0,1)) : plane(n, c) + { + //center(c); + //normal(n); + //init(); + R = (T)1; + } + + /*///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE + circle(vec3 c, T s, vec3 n = vec3(0,0,1)) : plane() + { + init(); + center(c); + rotate(n, U, Y); + scale(s); + } + */ + + ///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + ///@param u: x,y,z direction for the zero vector (from where the rotation starts) + CUDA_CALLABLE + circle(vec3 c, T r, vec3 n = vec3(0,0,1), vec3 u = vec3(1, 0, 0)) : plane() + { + P = c; + N = n; + setU(u); + R = r; + //init(); + //setU(u); +// U = u; + //center(c); + //normal(n); + //scale(s); + } + + ///scales the circle by a certain factor + ///@param factor: the factor by which the dimensions of the shape are scaled. + CUDA_CALLABLE + void scale(T factor) + { + //U *= factor; + //Y *= factor; + R *= factor; + } + + ///set the radius of circle to a certain value + ///@param value: the new radius of the circle + CUDA_CALLABLE + void set_R(T value) + { + R = value; + } + + ///sets the normal for the cirlce + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE void + normal(vec3 n){ + rotate(n); + } + + ///sets the center of the circle. + ///@param n: x,y,z location of the center. + CUDA_CALLABLE void + center(vec3 p){ + P = p; + } + + ///boolean comparison + bool + operator==(const circle & rhs) + { + if(P == rhs.P && U == rhs.U) + return true; + else + return false; + } + + //returns the point in world space corresponding to the polar coordinate (r, theta) + CUDA_CALLABLE stim::vec3 + p(T r, T theta) { + T u = r * cos(theta); //calculate the coordinates in the planar space defined by the circle + T v = r * sin(theta); + + vec3 V = U.cross(N); //calculate the orthogonal vector V + return P + U * u + V * v; //calculate the cartesian coordinate of the specified point + } + + //returns the point in world space corresponding to the value theta at radius R + CUDA_CALLABLE stim::vec3 + p(T theta) { + return p(R, theta); + } + + //get the world space value given the polar coordinates (r, theta) + + ///get the world space value given the planar coordinates a, b in [0, 1] + /*CUDA_CALLABLE stim::vec3 p(T a, T b) + { + stim::vec3 result; + + vec3 A = this->P - this->U * (T)0.5 - Y * (T)0.5; + result = A + this->U * a + Y * b; + return result; + }*/ + + ///parenthesis operator returns the world space given rectangular coordinates a and b in [0 1] + CUDA_CALLABLE stim::vec3 operator()(T r, T theta) + { + return p(r, theta); + } + + //parenthesis operator returns the world space coordinate at the edge of the circle given theta + CUDA_CALLABLE stim::vec3 operator()(T theta) { + return p(theta); + } + + ///returns a vector with the points on the initialized circle. + ///connecting the points results in a circle. + ///@param n: integer for the number of points representing the circle. + std::vector< stim::vec3 > points(unsigned n) { + std::vector< stim::vec3 > result(n); //initialize a vector of n points + + float dt = stim::TAU / n; + for (unsigned i = 0; i < n; i++) + result[i] = p(i * dt); //calculate a point on the edge of the circle + return result; + } + + ///returns a vector with the points on the initialized circle + ///connecting the points results in a circle + ///@param n: integer for the number of points representing the circle + ///the only difference between points and glpoints is that the first point appears twice in the returning lists + std::vector< stim::vec3 > glpoints(unsigned n) { + std::vector< stim::vec3 > result(n + 1); + float dt = stim::TAU / n; + for (unsigned i = 0; i < n; i++) + result[i] = p(i * dt); + result[n] = p(0); //close the circle! + return result; + } + + std::string str() const + { + std::stringstream ss; + ss << "r = "< +#include +#include +#include +#include +#include +#include +#include + +namespace stim{ + +template +class circle : plane +{ + +private: + + stim::vec3 Y; + + CUDA_CALLABLE void + init() + { + Y = U.cross(N).norm(); + } + +public: + using stim::plane::n; + using stim::plane::P; + using stim::plane::N; + using stim::plane::U; + using stim::plane::rotate; + using stim::plane::setU; + + ///base constructor + ///@param th value of the angle of the starting point from 0 to 360. + CUDA_CALLABLE + circle() : plane() + { + init(); + } + + ///create a rectangle given a size and position in Z space. + ///@param size: size of the rectangle in ND space. + ///@param z_pos z coordinate of the rectangle. + CUDA_CALLABLE + circle(T size, T z_pos = (T)0) : plane() + { + center(stim::vec3(0,0,z_pos)); + scale(size); + init(); + } + + ///create a rectangle from a center point, normal + ///@param c: x,y,z location of the center. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE + circle(vec3 c, vec3 n = vec3(0,0,1)) : plane() + { + center(c); + normal(n); + init(); + } + + /*///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE + circle(vec3 c, T s, vec3 n = vec3(0,0,1)) : plane() + { + init(); + center(c); + rotate(n, U, Y); + scale(s); + } + */ + + ///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + ///@param u: x,y,z direction for the zero vector (from where the rotation starts) + CUDA_CALLABLE + circle(vec3 c, T s, vec3 n = vec3(0,0,1), vec3 u = vec3(1, 0, 0)) : plane() + { + init(); + setU(u); +// U = u; + center(c); + normal(n); + scale(s); + } + + ///scales the circle by a certain factor + ///@param factor: the factor by which the dimensions of the shape are scaled. + CUDA_CALLABLE + void scale(T factor) + { + U *= factor; + Y *= factor; + } + + ///sets the normal for the cirlce + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE void + normal(vec3 n) + { + rotate(n, Y); + } + + ///sets the center of the circle. + ///@param n: x,y,z location of the center. + CUDA_CALLABLE void + center(vec3 p){ + this->P = p; + } + + ///boolean comparison + bool + operator==(const circle & rhs) + { + if(P == rhs.P && U == rhs.U && Y == rhs.Y) + return true; + else + return false; + } + + ///get the world space value given the planar coordinates a, b in [0, 1] + CUDA_CALLABLE stim::vec3 p(T a, T b) + { + stim::vec3 result; + + vec3 A = this->P - this->U * (T)0.5 - Y * (T)0.5; + result = A + this->U * a + Y * b; + return result; + } + + ///parenthesis operator returns the world space given rectangular coordinates a and b in [0 1] + CUDA_CALLABLE stim::vec3 operator()(T a, T b) + { + return p(a,b); + } + + ///returns a vector with the points on the initialized circle. + ///connecting the points results in a circle. + ///@param n: integer for the number of points representing the circle. + std::vector > + getPoints(int n) + { + std::vector > result; + stim::vec3 point; + T x,y; + float step = 360.0/(float) n; + for(float j = 0; j <= 360.0; j += step) + { + y = 0.5*cos(j*stim::TAU/360.0)+0.5; + x = 0.5*sin(j*stim::TAU/360.0)+0.5; + result.push_back(p(x,y)); + } + return result; + } + + ///returns a vector with the points on the initialized circle. + ///connecting the points results in a circle. + ///@param n: integer for the number of points representing the circle. + CUDA_CALLABLE stim::vec3 + p(T theta) + { + T x,y; + y = 0.5*cos(theta*STIM_TAU/360.0)+0.5; + x = 0.5*sin(theta*STIM_TAU/360.0)+0.5; + return p(x,y); + } + + std::string str() const + { + std::stringstream ss; + ss << "(P=" << P.str() << ", N=" << N.str() << ", U=" << U.str() << ", Y=" << Y.str(); + return ss.str(); + } + +}; +} +#endif diff --git a/tira/math/complex.h b/tira/math/complex.h new file mode 100644 index 0000000..b9b9c3b --- /dev/null +++ b/tira/math/complex.h @@ -0,0 +1,500 @@ +/// CUDA compatible complex number class + +#ifndef STIM_COMPLEX +#define STIM_COMPLEX + +#include "../cuda/cudatools/callable.h" +#include +#include +#include +#include + +namespace stim +{ + enum complexComponentType {complexFull, complexReal, complexImaginary, complexMag, complexIntensity}; + +template +struct complex +{ + T r, i; + + //default constructor + CUDA_CALLABLE complex() + { + r = 0; + i = 0; + } + + //constructor when given real and imaginary values + CUDA_CALLABLE complex(T r, T i = 0) + { + this->r = r; + this->i = i; + } + + //access methods + CUDA_CALLABLE T real() + { + return r; + } + + CUDA_CALLABLE T real(T r_val) + { + r = r_val; + return r_val; + } + + CUDA_CALLABLE T imag() + { + return i; + } + CUDA_CALLABLE T imag(T i_val) + { + i = i_val; + return i_val; + } + + + + //return the current value multiplied by i + CUDA_CALLABLE complex imul() + { + complex result; + result.r = -i; + result.i = r; + + return result; + } + + //returns the complex signum (-1, 0, 1) + CUDA_CALLABLE int sgn(){ + if(r > 0) return 1; + else if(r < 0) return -1; + else return (0 < i - i < 0); + } + + //ARITHMETIC OPERATORS-------------------- + + //binary + operator (returns the result of adding two complex values) + CUDA_CALLABLE complex operator+ (const complex rhs) const + { + complex result; + result.r = r + rhs.r; + result.i = i + rhs.i; + return result; + } + + CUDA_CALLABLE complex operator+ (const T rhs) const + { + complex result; + result.r = r + rhs; + result.i = i; + return result; + } + + //binary - operator (returns the result of adding two complex values) + CUDA_CALLABLE complex operator- (const complex rhs) const + { + complex result; + result.r = r - rhs.r; + result.i = i - rhs.i; + return result; + } + + //binary - operator (returns the result of adding two complex values) + CUDA_CALLABLE complex operator- (const T rhs) + { + complex result; + result.r = r - rhs; + result.i = i; + return result; + } + + //binary MULTIPLICATION operators (returns the result of multiplying complex values) + CUDA_CALLABLE complex operator* (const complex rhs) const + { + complex result; + result.r = r * rhs.r - i * rhs.i; + result.i = r * rhs.i + i * rhs.r; + return result; + } + CUDA_CALLABLE complex operator* (const T rhs) + { + return complex(r * rhs, i * rhs); + } + + //binary DIVISION operators (returns the result of dividing complex values) + CUDA_CALLABLE complex operator/ (const complex rhs) const + { + complex result; + T denom = rhs.r * rhs.r + rhs.i * rhs.i; + result.r = (r * rhs.r + i * rhs.i) / denom; + result.i = (- r * rhs.i + i * rhs.r) / denom; + + return result; + } + CUDA_CALLABLE complex operator/ (const T rhs) + { + return complex(r / rhs, i / rhs); + } + + //ASSIGNMENT operators----------------------------------- + CUDA_CALLABLE complex & operator=(const complex &rhs) + { + //check for self-assignment + if(this != &rhs) + { + this->r = rhs.r; + this->i = rhs.i; + } + return *this; + } + CUDA_CALLABLE complex & operator=(const T &rhs) + { + this->r = rhs; + this->i = 0; + + return *this; + } + + //arithmetic assignment operators + CUDA_CALLABLE complex operator+=(const complex &rhs) + { + *this = *this + rhs; + return *this; + } + CUDA_CALLABLE complex operator+=(const T &rhs) + { + *this = *this + rhs; + return *this; + } + + CUDA_CALLABLE complex operator-=(const complex &rhs) + { + *this = *this - rhs; + return *this; + } + CUDA_CALLABLE complex operator-=(const T &rhs) + { + *this = *this - rhs; + return *this; + } + + CUDA_CALLABLE complex operator*=(const complex &rhs) + { + *this = *this * rhs; + return *this; + } + CUDA_CALLABLE complex operator*=(const T &rhs) + { + *this = *this * rhs; + return *this; + } + //divide and assign + CUDA_CALLABLE complex operator/=(const complex &rhs) + { + *this = *this / rhs; + return *this; + } + CUDA_CALLABLE complex operator/=(const T &rhs) + { + *this = *this / rhs; + return *this; + } + + //absolute value operator (returns the absolute value of the complex number) + CUDA_CALLABLE T abs() + { + return std::sqrt(r * r + i * i); + } + + CUDA_CALLABLE complex log() + { + complex result; + result.r = (T)std::log(std::sqrt(r * r + i * i)); + result.i = (T)std::atan2(i, r); + + + return result; + } + + CUDA_CALLABLE complex exp() + { + complex result; + + T e_r = std::exp(r); + result.r = e_r * (T)std::cos(i); + result.i = e_r * (T)std::sin(i); + + return result; + } + + CUDA_CALLABLE complex pow(T y) + { + complex result; + + result = log() * y; + + return result.exp(); + } + + CUDA_CALLABLE complex sqrt() + { + complex result; + + //convert to polar coordinates + T a = std::sqrt(r*r + i*i); + T theta = std::atan2(i, r); + + //find the square root + T a_p = std::sqrt(a); + T theta_p = theta/2.0f; + + //convert back to cartesian coordinates + result.r = a_p * std::cos(theta_p); + result.i = a_p * std::sin(theta_p); + + return result; + } + + std::string str() + { + std::stringstream ss; + ss<<"("< rhs) + { + if(r == rhs.r && i == rhs.i) + return true; + return false; + } + + CUDA_CALLABLE bool operator==(T rhs) + { + if(r == rhs && i == 0) + return true; + return false; + } + + CUDA_CALLABLE bool operator!=(T rhs) + { + if(r != rhs || i != 0) + return true; + return false; + } + + CUDA_CALLABLE bool operator<(complex rhs){ + return abs() < rhs.abs(); + } + CUDA_CALLABLE bool operator<=(complex rhs){ + return abs() <= rhs.abs(); + } + CUDA_CALLABLE bool operator>(complex rhs){ + return abs() > rhs.abs(); + } + CUDA_CALLABLE bool operator >=(complex rhs){ + return abs() >= rhs.abs(); + } + + //CASTING operators + template < typename otherT > + operator complex() + { + complex result((otherT)r, (otherT)i); + return result; + } + template< typename otherT > + complex( const complex &rhs) + { + r = (T)rhs.r; + i = (T)rhs.i; + } + template< typename otherT > + complex& operator=(const complex &rhs) + { + r = (T)rhs.r; + i = (T)rhs.i; + return *this; + } + + + +}; + +/// Cast an array of complex values to an array of real values +template +static void real(T* r, complex* c, size_t n){ + for(size_t i = 0; i < n; i++) + r[i] = c[i].real(); +} + +/// Cast an array of complex values to an array of real values +template +static void imag(T* r, complex* c, size_t n){ + for(size_t i = 0; i < n; i++) + r[i] = c[i].imag(); +} + +/// Calculate the magnitude of an array of complex values +template +static void abs(T* m, complex* c, size_t n){ + for(size_t i = 0; i < n; i++) + m[i] = c[i].abs(); +} + +/// Calculate the intensity of an array of complex values +template +static void intensity(T* m, complex* c, size_t n){ + for(size_t i = 0; i < n; i++) + m[i] = pow(c[i].abs(), 2); +} + +} //end RTS namespace + +//addition +template +CUDA_CALLABLE static stim::complex operator+(const double a, const stim::complex b) +{ + return stim::complex((T)a + b.r, b.i); +} + +//subtraction with a real value +template +CUDA_CALLABLE static stim::complex operator-(const double a, const stim::complex b) +{ + return stim::complex((T)a - b.r, -b.i); +} + +//minus sign +template +CUDA_CALLABLE static stim::complex operator-(const stim::complex &rhs) +{ + return stim::complex(-rhs.r, -rhs.i); +} + +//multiply a T value by a complex value +template +CUDA_CALLABLE static stim::complex operator*(const double a, const stim::complex b) +{ + return stim::complex((T)a * b.r, (T)a * b.i); +} + +//divide a T value by a complex value +template +CUDA_CALLABLE static stim::complex operator/(const double a, const stim::complex b) +{ + stim::complex result; + + T denom = b.r * b.r + b.i * b.i; + + result.r = ((T)a * b.r) / denom; + result.i = -((T)a * b.i) / denom; + + return result; +} + + +template +CUDA_CALLABLE static stim::complex pow(stim::complex x, T y) +{ + return x.pow(y); +} +template +CUDA_CALLABLE static stim::complex pow(stim::complex x, int y) +{ + return x.pow(y); +} + +//log function +template +CUDA_CALLABLE static stim::complex log(stim::complex x) +{ + return x.log(); +} + +//exp function +template +CUDA_CALLABLE static stim::complex exp(stim::complex x) +{ + return x.exp(); +} + +//sqrt function +template +CUDA_CALLABLE static stim::complex sqrt(stim::complex x) +{ + return x.sqrt(); +} + + +template +CUDA_CALLABLE static T abs(stim::complex a) +{ + return a.abs(); +} + +template +CUDA_CALLABLE static T real(stim::complex a) +{ + return a.r; +} + +//template +CUDA_CALLABLE static float real(float a) +{ + return a; +} + +template +CUDA_CALLABLE static T imag(stim::complex a) +{ + return a.i; +} + +template +CUDA_CALLABLE stim::complex sin(const stim::complex x) +{ + stim::complex result; + result.r = (A)std::sin(x.r) * (A)std::cosh(x.i); + result.i = (A)std::cos(x.r) * (A)std::sinh(x.i); + + return result; +} + +template +CUDA_CALLABLE stim::complex cos(const stim::complex x) +{ + stim::complex result; + result.r = (A)std::cos(x.r) * (A)std::cosh(x.i); + result.i = -((A)std::sin(x.r) * (A)std::sinh(x.i)); + + return result; +} + + +template +std::ostream& operator<<(std::ostream& os, stim::complex x) +{ + os< +std::istream& operator>>(std::istream& is, stim::complex& x) +{ + A r, i; + r = i = 0; //initialize the real and imaginary parts to zero + is>>r; //parse + is>>i; + + x.real(r); //assign the parsed values to x + x.imag(i); + + return is; //return the stream +} + +#endif diff --git a/tira/math/complexfield.cuh b/tira/math/complexfield.cuh new file mode 100644 index 0000000..701e97e --- /dev/null +++ b/tira/math/complexfield.cuh @@ -0,0 +1,137 @@ +#ifndef RTS_COMPLEXFIELD_H +#define RTS_COMPLEXFIELD_H + +#include "cublas_v2.h" +#include + +#include "../math/field.cuh" +#include "../math/complex.h" +#include "../math/realfield.cuh" + +namespace stim{ + +template +__global__ void gpu_complexfield_mag(T* dest, complex* source, unsigned int r0, unsigned int r1){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //calculate and store the result + dest[i] = source[i].abs(); +} + +/*This class stores functions for saving images of complex fields +*/ +template +class complexfield : public field< stim::complex, D >{ + using field< stim::complex, D >::R; + using field< stim::complex, D >::X; + using field< stim::complex, D >::shape; + using field< stim::complex, D >::cuda_params; + + + +public: + + //find the maximum value of component n + stim::complex find_max(unsigned int n){ + cublasStatus_t stat; + cublasHandle_t handle; + + //create a CUBLAS handle + stat = cublasCreate(&handle); + if(stat != CUBLAS_STATUS_SUCCESS){ + std::cout<<"CUBLAS Error: initialization failed"< result; + + if(sizeof(T) == 8) + stat = cublasIcamax(handle, L, (const cuComplex*)X[n], 1, &index); + else + stat = cublasIzamax(handle, L, (const cuDoubleComplex*)X[n], 1, &index); + + index -= 1; //adjust for 1-based indexing + + //if there was a GPU error, terminate + if(stat != CUBLAS_STATUS_SUCCESS){ + std::cout<<"CUBLAS Error: failure finding maximum value."<), cudaMemcpyDeviceToHost)); + return result; + } + +public: + + enum attribute {magnitude, real, imaginary}; + + //constructor (no parameters) + complexfield() : field, D>(){}; + + //constructor (resolution specified) + complexfield(unsigned int r0, unsigned int r1) : field, D>(r0, r1){}; + + //assignment from a field of complex values + complexfield & operator=(const field< stim::complex, D > rhs){ + field< complex, D >::operator=(rhs); + return *this; + } + + //assignment operator (scalar value) + complexfield & operator= (const complex rhs){ + + field< complex, D >::operator=(rhs); + return *this; + } + + //assignment operator (vector value) + complexfield & operator= (const vec< complex, D > rhs){ + + field< complex, D >::operator=(rhs); + return *this; + } + + //cropping + complexfield crop(unsigned int width, unsigned int height){ + + complexfield result; + result = field< complex, D>::crop(width, height); + return result; + } + + void toImage(std::string filename, attribute type = magnitude, unsigned int n=0){ + + field rf(R[0], R[1]); + + //get cuda parameters + dim3 blocks, grids; + cuda_params(grids, blocks); + + if(type == magnitude){ + gpu_complexfield_mag <<>> (rf.ptr(), X[n], R[0], R[1]); + rf.toImage(filename, n, true); + } + + } + + +}; + + +} //end namespace rts + + +#endif diff --git a/tira/math/constants.h b/tira/math/constants.h new file mode 100644 index 0000000..211375b --- /dev/null +++ b/tira/math/constants.h @@ -0,0 +1,12 @@ +#ifndef STIM_CONSTANTS_H +#define STIM_CONSTANTS_H + +#define STIM_PI 3.1415926535897932384626433832795028841971693993751058209749445923078164062862 +#define STIM_TAU 2 * STIM_PI + +namespace stim{ + const double PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862; + const double TAU = 2 * stim::PI; +} + +#endif diff --git a/tira/math/fd_coefficients.h b/tira/math/fd_coefficients.h new file mode 100644 index 0000000..764d0ce --- /dev/null +++ b/tira/math/fd_coefficients.h @@ -0,0 +1,67 @@ +#ifndef STIM_FINITE_DIFFERENCE_COEFFICIENTS +#define STIM_FINITE_DIFFERENCE_COEFFICIENTS + +#include +#include + +/// Calculate the coefficients used for finite differences on arbitrary grids. This code is based on: +/// Fornberg, "Generation of Finite Difference Formulas on Arbitrarily Spaced Grids", Mathematics of Computation, 51(184) 699-706, 1988 + +/// @param Z is the location where the approximation is to be accurate +/// @param X is a vector of coordinates for grid points +/// @param M is the highest derivative to be computed +/// This function returns a matrix C[a][b] where a is the derivative and b is the coefficient for x = b + +template +std::vector< std::vector > diff_coefficient_matrix(T Z, std::vector X, size_t M){ + size_t n = X.size(); //calculate the number of data points available + std::vector< std::vector > C(M+1); //create an array of m arrays + for(size_t m = 0; m < M+1; m++) //for each derivative + C[m].resize(n); //allocate space for n coefficients + + T c1, c2, c3, c4, c5; + c1 = 1; //initialize the matrix + c4 = X[0] - Z; + C[0][0] = 1; + for(size_t i = 2; i <= n; i++){ + size_t mn = (std::min)(i, M+1); + c2 = 1; + c5 = c4; + c4 = X[i-1] - Z; + for(size_t j = 1; j <= i-1; j++){ + c3 = X[i-1] - X[j-1]; + c2 = c2 * c3; + if(j == i-1){ + for(size_t k = 2; k <= mn; k++){ + C[k-1][i-1] = c1 * ((k-1) * C[k-2][i-2] - c5 * C[k-1][i-2]) / c2; + } + C[0][i-1] = -c1 * c5 * C[0][i-2]/c2; + } + T c_p0 = C[0][j-1]; + T c_p1 = C[1][j-1]; + for(size_t k = 2; k <= mn; k++){ + c_p1 = C[k-1][j-1]; + C[k-1][j-1] = (c4 * c_p1 - (k-1) * c_p0) / c3; + c_p0 = c_p1; + } + C[0][j-1] = c4 * C[0][j-1] / c3; + } + c1 = c2; + } + return C; +} + +/// Returns the coefficients used to estimate the nth derivative at x0 given values at points in x + +/// @param x0 is the point where the derivative f'(x0) is to be estimated +/// @param x is the set of points where the values of f(x) are known +/// @param n specifies the derivative, ex. 2 estimates the second derivative f''(x0) +template +std::vector diff_coefficients(T x0, std::vector x, size_t n){ + std::vector< std::vector > C = diff_coefficient_matrix(x0, x, n); //calculate all derivative coefficients + return C.back(); +} + + + +#endif \ No newline at end of file diff --git a/tira/math/fft.h b/tira/math/fft.h new file mode 100644 index 0000000..d3e9448 --- /dev/null +++ b/tira/math/fft.h @@ -0,0 +1,46 @@ +#ifndef STIM_FFT_H +#define STIM_FFT_H + +namespace stim{ + + /*template + void circshift(T *out, const T *in, size_t xdim, size_t ydim, size_t xshift, size_t yshift){ + size_t i, j, ii, jj; + for (i =0; i < xdim; i++) { + ii = (i + xshift) % xdim; + for (j = 0; j < ydim; j++) { + jj = (j + yshift) % ydim; + out[ii * ydim + jj] = in[i * ydim + j]; + } + } + }*/ + + template + void circshift(T *out, const T *in, int xdim, int ydim, int xshift, int yshift) + { + for (int i =0; i < xdim; i++) { + int ii = (i + xshift) % xdim; + if (ii<0) ii = xdim + ii; + for (int j = 0; j < ydim; j++) { + int jj = (j + yshift) % ydim; + if (jj<0) jj = ydim + jj; + //out[ii * ydim + jj] = in[i * ydim + j]; + out[jj * xdim + ii] = in[j * xdim + i]; + } + } + } + + template + void cpu_fftshift(T* out, T* in, size_t xdim, size_t ydim){ + circshift(out, in, xdim, ydim, std::floor(xdim/2), std::floor(ydim/2)); + } + + template + void cpu_ifftshift(T* out, T* in, size_t xdim, size_t ydim){ + circshift(out, in, xdim, ydim, std::ceil(xdim/2), std::ceil(ydim/2)); + } + + +} + +#endif \ No newline at end of file diff --git a/tira/math/field.cuh b/tira/math/field.cuh new file mode 100644 index 0000000..aa6b10c --- /dev/null +++ b/tira/math/field.cuh @@ -0,0 +1,343 @@ +#ifndef RTS_FIELD_CUH +#define RTS_FIELD_CUH + +#include +#include +#include + +#include "cublas_v2.h" +#include + +#include "../math/rect.h" +#include "../cuda/threads.h" +#include "../cuda/error.h" +#include "../cuda/devices.h" +#include "../visualization/colormap.h" + + +namespace stim{ + +//multiply R = X * Y +template +__global__ void gpu_field_multiply(T* R, T* X, T* Y, unsigned int r0, unsigned int r1){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //calculate and store the result + R[i] = X[i] * Y[i]; +} + +//assign a constant value to all points +template +__global__ void gpu_field_assign(T* ptr, T val, unsigned int r0, unsigned int r1){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //calculate and store the result + ptr[i] = val; +} + +//crop the field to the new dimensions (width x height) +template +__global__ void gpu_field_crop(T* dest, T* source, + unsigned int r0, unsigned int r1, + unsigned int width, unsigned int height){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= width || iv >= height) return; + + //compute the index into the field + int is = iv*r0 + iu; + int id = iv*width + iu; + + //calculate and store the result + dest[id] = source[is]; +} + +template +class field{ + +protected: + + T* X[D]; //pointer to the field data + unsigned int R[2]; //field resolution + stim::rect shape; //position and shape of the field slice + + //calculates the optimal block and grid sizes using information from the GPU + void cuda_params(dim3& grids, dim3& blocks){ + int maxThreads = stim::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + blocks = dim3(SQRT_BLOCK, SQRT_BLOCK); + grids = dim3((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + } + + //find the maximum value of component n + T find_max(unsigned int n){ + cublasStatus_t stat; + cublasHandle_t handle; + + //create a CUBLAS handle + stat = cublasCreate(&handle); + if(stat != CUBLAS_STATUS_SUCCESS){ + std::cout<<"CUBLAS Error: initialization failed"< process_filename(std::string name){ + std::stringstream ss(name); + std::string item; + std::vector elems; + while(std::getline(ss, item, '.')) //split the string at the '.' character (filename and extension) + { + elems.push_back(item); + } + + std::string prefix = elems[0]; //prefix contains the filename (with wildcard '?' characters) + std::string ext = elems[1]; //file extension (ex. .bmp, .png) + ext = std::string(".") + ext; //add a period back into the extension + + size_t i0 = prefix.find_first_of("?"); //find the positions of the first and last wildcard ('?'') + size_t i1 = prefix.find_last_of("?"); + + std::string postfix = prefix.substr(i1+1); + prefix = prefix.substr(0, i0); + + unsigned int digits = i1 - i0 + 1; //compute the number of wildcards + + std::vector flist; //create a vector of file names + //fill the list + for(unsigned int d=0; d>> (X[n], rhs, R[0], R[1]); + + return *this; + } + + //assignment of vector component + field & operator= (const vec rhs){ + + int maxThreads = stim::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //assign the constant value to all positions and dimensions + for(unsigned int n=0; n>> (X[n], rhs.v[n], R[0], R[1]); + + return *this; + + } + + //multiply two fields (element-wise multiplication) + field operator* (const field & rhs){ + + int maxThreads = stim::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //create a scalar field to store the result + field result(R[0], R[1]); + + for(int n=0; n>> (result.X[n], X[n], rhs.X[n], R[0], R[1]); + + return result; + } + + T* ptr(unsigned int n = 0){ + if(n < D) + return X[n]; + else return NULL; + } + + //return the vector component at position (u, v) + vec get(unsigned int u, unsigned int v){ + + vec result; + for(unsigned int d=0; d crop(unsigned int width, unsigned int height){ + int maxThreads = stim::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((width + SQRT_BLOCK -1)/SQRT_BLOCK, (height + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //create a scalar field to store the result + field result(width, height); + + for(int n=0; n>> (result.X[n], X[n], R[0], R[1], width, height); + + return result; + } + + //save an image representing component n + void toImage(std::string filename, unsigned int n = 0, + bool positive = false, stim::colormapType cmap = stim::cmBrewer){ + T max_val = find_max(n); //find the maximum value + + if(positive) //if the field is positive, use the range [0 max_val] + stim::gpu2image(X[n], filename, R[0], R[1], 0, max_val, cmap); + else + stim::gpu2image(X[n], filename, R[0], R[1], -max_val, max_val, cmap); + } + +}; + +} //end namespace rts +#endif diff --git a/tira/math/filters/conv2.cuh b/tira/math/filters/conv2.cuh new file mode 100644 index 0000000..48ea7f7 --- /dev/null +++ b/tira/math/filters/conv2.cuh @@ -0,0 +1,119 @@ +#ifndef STIM_CUDA_CONV2_H +#define STIM_CUDA_CONV2_H +//#define __CUDACC__ + +#include +#include + +namespace stim { + //Kernel function that performs the 2D convolution. + template + __global__ void kernel_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + extern __shared__ T s[]; //declare a shared memory array + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; //threads correspond to indices into the output image + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + size_t tid = threadIdx.y * blockDim.x + threadIdx.x; + size_t nt = blockDim.x * blockDim.y; + + size_t cx = blockIdx.x * blockDim.x; //find the upper left corner of the input region + size_t cy = blockIdx.y * blockDim.y; + + size_t X = sx - kx + 1; //calculate the size of the output image + size_t Y = sy - ky + 1; + + if (cx >= X || cy >= Y) return; //return if the entire block is outside the image + size_t smx = min(blockDim.x + kx - 1, sx - cx); //size of the shared copy of the input image + size_t smy = min(blockDim.y + ky - 1, sy - cy); // min function is used to deal with boundary blocks + stim::cuda::threadedMemcpy2D(s, smx, smy, in, cx, cy, sx, sy, tid, nt); //copy the input region to shared memory + __syncthreads(); + + if (xi >= X || yi >= Y) return; //returns if the thread is outside of the output image + + //loop through the kernel + size_t kxi, kyi; + K v = 0; + for (kyi = 0; kyi < ky; kyi++) { + for (kxi = 0; kxi < kx; kxi++) { + v += s[(threadIdx.y + kyi) * smx + threadIdx.x + kxi] * kernel[kyi * kx + kxi]; + //v += in[(yi + kyi) * sx + xi + kxi] * kernel[kyi * kx + kxi]; + } + } + out[yi * X + xi] = (T)v; //write the result to global memory + + } + + //Performs a convolution of a 2D image using the GPU. All pointers are assumed to be to memory on the current device. + //@param out is a pointer to the output image, which is of size (sx - kx + 1) x (sy - ky + 1) + //@param in is a pointer to the input image + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void gpu_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + cudaDeviceProp p; + HANDLE_ERROR(cudaGetDeviceProperties(&p, 0)); + size_t tmax = p.maxThreadsPerBlock; + dim3 nt(sqrt(tmax), sqrt(tmax)); //calculate the block dimensions + size_t X = sx - kx + 1; //calculate the size of the output image + size_t Y = sy - ky + 1; + dim3 nb(X / nt.x + 1, Y / nt.y + 1); //calculate the grid dimensions + size_t sm = (nt.x + kx - 1) * (nt.y + ky - 1) * sizeof(T); //shared memory bytes required to store block data + if (sm > p.sharedMemPerBlock) { + std::cout << "Error in stim::gpu_conv2() - insufficient shared memory for this kernel." << std::endl; + exit(1); + } + kernel_conv2 <<>> (out, in, kernel, sx, sy, kx, ky); //launch the kernel + } +//#endif + //Performs a convolution of a 2D image. Only valid pixels based on the kernel are returned. + // As a result, the output image will be smaller than the input image by (kx-1, ky-1) + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void cpu_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + size_t X = sx - kx + 1; //x size of the output image + size_t Y = sy - ky + 1; //y size of the output image + + //allocate memory and copy everything to the GPU + T* gpu_in; + HANDLE_ERROR(cudaMalloc(&gpu_in, sx * sy * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpu_in, in, sx * sy * sizeof(T), cudaMemcpyHostToDevice)); + K* gpu_kernel; + HANDLE_ERROR(cudaMalloc(&gpu_kernel, kx * ky * sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_kernel, kernel, kx * ky * sizeof(K), cudaMemcpyHostToDevice)); + T* gpu_out; + HANDLE_ERROR(cudaMalloc(&gpu_out, X * Y * sizeof(T))); + gpu_conv2(gpu_out, gpu_in, gpu_kernel, sx, sy, kx, ky); //execute the GPU kernel + HANDLE_ERROR(cudaMemcpy(out, gpu_out, X * Y * sizeof(T), cudaMemcpyDeviceToHost)); //copy the result to the host + HANDLE_ERROR(cudaFree(gpu_in)); + HANDLE_ERROR(cudaFree(gpu_kernel)); + HANDLE_ERROR(cudaFree(gpu_out)); +/* CPU CODE + K v; //register stores the integral of the current pixel value + size_t yi, xi, kyi, kxi, yi_kyi_sx; + for (yi = 0; yi < Y; yi++) { //for each pixel in the output image + for (xi = 0; xi < X; xi++) { + v = 0; + for (kyi = 0; kyi < ky; kyi++) { //for each pixel in the kernel + yi_kyi_sx = (yi + kyi) * sx; + for (kxi = 0; kxi < kx; kxi++) { + v += in[yi_kyi_sx + xi + kxi] * kernel[kyi * kx + kxi]; + } + } + out[yi * X + xi] = v; //save the result to the output array + } + } + + */ + } + + +} + + +#endif \ No newline at end of file diff --git a/tira/math/filters/conv2.h b/tira/math/filters/conv2.h new file mode 100644 index 0000000..e34ea61 --- /dev/null +++ b/tira/math/filters/conv2.h @@ -0,0 +1,123 @@ +#ifndef STIM_CUDA_CONV2_H +#define STIM_CUDA_CONV2_H +//#define __CUDACC__ + +#ifdef __CUDACC__ +#include +#include +#endif + +namespace stim { +#ifdef __CUDACC__ + //Kernel function that performs the 2D convolution. + template + __global__ void kernel_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + extern __shared__ T s[]; //declare a shared memory array + size_t xi = blockIdx.x * blockDim.x + threadIdx.x; //threads correspond to indices into the output image + size_t yi = blockIdx.y * blockDim.y + threadIdx.y; + size_t tid = threadIdx.y * blockDim.x + threadIdx.x; + size_t nt = blockDim.x * blockDim.y; + + size_t cx = blockIdx.x * blockDim.x; //find the upper left corner of the input region + size_t cy = blockIdx.y * blockDim.y; + + size_t X = sx - kx + 1; //calculate the size of the output image + size_t Y = sy - ky + 1; + + if (cx >= X || cy >= Y) return; //return if the entire block is outside the image + size_t smx = min(blockDim.x + kx - 1, sx - cx); //size of the shared copy of the input image + size_t smy = min(blockDim.y + ky - 1, sy - cy); // min function is used to deal with boundary blocks + stim::cuda::threadedMemcpy2D(s, smx, smy, in, cx, cy, sx, sy, tid, nt); //copy the input region to shared memory + __syncthreads(); + + if (xi >= X || yi >= Y) return; //returns if the thread is outside of the output image + + //loop through the kernel + size_t kxi, kyi; + K v = 0; + for (kyi = 0; kyi < ky; kyi++) { + for (kxi = 0; kxi < kx; kxi++) { + v += s[(threadIdx.y + kyi) * smx + threadIdx.x + kxi] * kernel[kyi * kx + kxi]; + //v += in[(yi + kyi) * sx + xi + kxi] * kernel[kyi * kx + kxi]; + } + } + out[yi * X + xi] = (T)v; //write the result to global memory + + } + + //Performs a convolution of a 2D image using the GPU. All pointers are assumed to be to memory on the current device. + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void gpu_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + cudaDeviceProp p; + HANDLE_ERROR(cudaGetDeviceProperties(&p, 0)); + size_t tmax = p.maxThreadsPerBlock; + dim3 nt(sqrt(tmax), sqrt(tmax)); //calculate the block dimensions + size_t X = sx - kx + 1; //calculate the size of the output image + size_t Y = sy - ky + 1; + dim3 nb(X / nt.x + 1, Y / nt.y + 1); //calculate the grid dimensions + size_t sm = (nt.x + kx - 1) * (nt.y + ky - 1) * sizeof(T); //shared memory bytes required to store block data + if (sm > p.sharedMemPerBlock) { + std::cout << "Error in stim::gpu_conv2() - insufficient shared memory for this kernel." << std::endl; + exit(1); + } + kernel_conv2 <<>> (out, in, kernel, sx, sy, kx, ky); //launch the kernel + } +#endif + //Performs a convolution of a 2D image. Only valid pixels based on the kernel are returned. + // As a result, the output image will be smaller than the input image by (kx-1, ky-1) + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void cpu_conv2(T* out, T* in, K* kernel, size_t sx, size_t sy, size_t kx, size_t ky) { + size_t X = sx - kx + 1; //x size of the output image + size_t Y = sy - ky + 1; //y size of the output image + +#ifdef __CUDACC__ + //allocate memory and copy everything to the GPU + T* gpu_in; + HANDLE_ERROR(cudaMalloc(&gpu_in, sx * sy * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpu_in, in, sx * sy * sizeof(T), cudaMemcpyHostToDevice)); + K* gpu_kernel; + HANDLE_ERROR(cudaMalloc(&gpu_kernel, kx * ky * sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_kernel, kernel, kx * ky * sizeof(K), cudaMemcpyHostToDevice)); + T* gpu_out; + HANDLE_ERROR(cudaMalloc(&gpu_out, X * Y * sizeof(T))); + gpu_conv2(gpu_out, gpu_in, gpu_kernel, sx, sy, kx, ky); //execute the GPU kernel + HANDLE_ERROR(cudaMemcpy(out, gpu_out, X * Y * sizeof(T), cudaMemcpyDeviceToHost)); //copy the result to the host + HANDLE_ERROR(cudaFree(gpu_in)); + HANDLE_ERROR(cudaFree(gpu_kernel)); + HANDLE_ERROR(cudaFree(gpu_out)); +#else + K v; //register stores the integral of the current pixel value + size_t yi, xi, kyi, kxi, yi_kyi_sx; + for (yi = 0; yi < Y; yi++) { //for each pixel in the output image + for (xi = 0; xi < X; xi++) { + v = 0; + for (kyi = 0; kyi < ky; kyi++) { //for each pixel in the kernel + yi_kyi_sx = (yi + kyi) * sx; + for (kxi = 0; kxi < kx; kxi++) { + v += in[yi_kyi_sx + xi + kxi] * kernel[kyi * kx + kxi]; + } + } + out[yi * X + xi] = v; //save the result to the output array + } + } + +#endif + } + + +} + + +#endif \ No newline at end of file diff --git a/tira/math/filters/gauss2.cuh b/tira/math/filters/gauss2.cuh new file mode 100644 index 0000000..bd23d42 --- /dev/null +++ b/tira/math/filters/gauss2.cuh @@ -0,0 +1,47 @@ +#ifndef STIM_CUDA_GAUSS2_H +#define STIM_CUDA_GAUSS2_H + +#include +#include +#include + +namespace stim { + + template + T gauss1d(T x, T mu, T std) { + return ((T)1 / (T)sqrt(stim::TAU * std * std) * exp(-(x - mu)*(x - mu) / (2 * std*std))); + } + ///Perform a 2D gaussian convolution on an input image. + ///@param in is a pointer to the input image + ///@param stdx is the standard deviation (in pixels) along the x axis + ///@param stdy is the standard deviation (in pixels) along the y axis + ///@param nstds specifies the number of standard deviations of the Gaussian that will be kept in the kernel + template + stim::image cpu_gauss2(const stim::image& in, K stdx, K stdy, size_t nstds = 3) { + size_t kx = (size_t)(stdx * nstds * 2) + 1; //calculate the kernel sizes + size_t ky = (size_t)(stdy * nstds * 2) + 1; + size_t X = in.width() - kx + 1; //calculate the size of the output image + size_t Y = in.height() - ky + 1; + stim::image r(X, Y, in.channels()); //create an output image + + K* gx = (K*)malloc(kx * sizeof(K)); //allocate space for the gaussian kernels + K* gy = (K*)malloc(ky * sizeof(K)); + K mux = (K)kx / (K)2; //calculate the mean of the gaussian (center of the kernel) + K muy = (K)ky / (K)2; + for (size_t xi = 0; xi < kx; xi++) //evaluate the kernels + gx[xi] = gauss1d((K)xi, mux, stdx); + for (size_t yi = 0; yi < ky; yi++) + gy[yi] = gauss1d((K)yi, muy, stdy); + + std::vector< stim::image > clist = in.split(); //split the input image into channels + std::vector< stim::image > R = r.split(); //split the output image into channels + for (size_t c = 0; c < in.channels(); c++) //for each channel + cpu_sepconv2(R[c].data(), clist[c].data(), gx, gy, clist[c].width(), clist[c].height(), kx, ky); + + r.merge(R); //merge the blurred channels into the final image + return r; + } +} + + +#endif \ No newline at end of file diff --git a/tira/math/filters/gauss3.cuh b/tira/math/filters/gauss3.cuh new file mode 100644 index 0000000..4b72dd9 --- /dev/null +++ b/tira/math/filters/gauss3.cuh @@ -0,0 +1,55 @@ +#ifndef STIM_CUDA_GAUSS3_H +#define STIM_CUDA_GAUSS3_H +#include +#include +#include + +namespace stim +{ + ///Perform a 3D gaussian convolution on an input image. + ///@param in is a pointer to the input data. + ///@param dimx is the size of in* in the x direction. + ///@param dimx is the size of in* in the y direction. + ///@param dimx is the size of in* in the z direction. + ///@param stdx is the standard deviation (in pixels) along the x axis. + ///@param stdy is the standard deviation (in pixels) along the y axis. + ///@param nstds specifies the number of standard deviations of the Gaussian that will be kept in the kernel. + template + void cpu_gauss3(T* in, K dimx, K dimy, K dimz, K stdx, K stdy, K stdz, size_t nstds = 3) + { + //Set up the sizes of the gaussian Kernels. + size_t kx = stdx * nstds * 2; + size_t ky = stdy * nstds * 2; + size_t kz = stdz * nstds * 2; + + //Set up the sizes of the new output, which will be kx, ky, kz, smaller than the input. + size_t X = dimx - kx +1; + size_t Y = dimy - ky +1; + size_t Z = dimz - kz +1; + T* out = (T*) malloc(X*Y*Z* sizeof(T)); + + ///Set up the memory that will store the gaussians + K* gaussx = (K*)malloc(kx *sizeof(K)); + K* gaussy = (K*)malloc(ky *sizeof(K)); + K* gaussz = (K*)malloc(kz *sizeof(K)); + + ///Set up the midpoints of the gaussians. + K midgaussx = (K) kx/ (K)2; + K midgaussy = (K) ky/ (K)2; + K midgaussz = (K) kz/ (K)2; + + ///Evaluate the kernels in each cardinal direction. + for(size_t i = 0; i < kx; i++) + gaussx[i] = gauss1d((K) i, midgaussx, stdx); + + for(size_t i = 0; i < kx; i++) + gaussy[i] = gauss1d((K) i, midgaussy, stdy); + + for(size_t i = 0; i < kx; i++) + gaussz[i] = gauss1d((K) i, midgaussz, stdz); + + cpu_sepconv3(out, in, gaussx, gaussy, gaussz, dimx, dimy, dimz, kx, ky, kz); + + } +} +#endif diff --git a/tira/math/filters/gradient.cuh b/tira/math/filters/gradient.cuh new file mode 100644 index 0000000..88dd259 --- /dev/null +++ b/tira/math/filters/gradient.cuh @@ -0,0 +1,100 @@ +#ifndef STIM_CUDA_GRADIENT_H +#define STIM_CUDA_GRADIENT_H + +#include +#include +#include + +namespace stim{ + namespace cuda{ + + template + __global__ void gradient_2d(T* out, T* in, int x, int y){ + + + // calculate the 2D coordinates for this current thread. + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + // convert 2D coordinates to 1D + int i = yi * x + xi; + + //return if the pixel is outside of the image + if(xi >= x || yi >= y) return; + + //calculate indices for the forward difference + int i_xp = yi * x + (xi + 1); + int i_yp = (yi + 1) * x + xi; + + //calculate indices for the backward difference + int i_xn = yi * x + (xi - 1); + int i_yn = (yi - 1) * x + xi; + + //use forward differences if a coordinate is zero + if(xi == 0) + out[i * 2 + 0] = in[i_xp] - in[i]; + if(yi == 0) + out[i * 2 + 1] = in[i_yp] - in[i]; + + //use backward differences if the coordinate is at the maximum edge + if(xi == x-1) + out[i * 2 + 0] = in[i] - in[i_xn]; + if(yi == y-1) + out[i * 2 + 1] = in[i] - in[i_yn]; + + //otherwise use central differences + if(xi > 0 && xi < x-1) + out[i * 2 + 0] = (in[i_xp] - in[i_xn]) / 2; + + if(yi > 0 && yi < y-1) + out[i * 2 + 1] = (in[i_yp] - in[i_yn]) / 2; + + } + + template + void gpu_gradient_2d(T* gpuGrad, T* gpuI, unsigned int x, unsigned int y){ + + //get the maximum number of threads per block for the CUDA device + unsigned int max_threads = stim::maxThreadsPerBlock(); + dim3 threads(max_threads, 1); + dim3 blocks(x/threads.x + 1 , y); + + + //call the GPU kernel to determine the gradient + gradient_2d <<< blocks, threads >>>(gpuGrad, gpuI, x, y); + + } + + template + void cpu_gradient_2d(T* out, T* in, unsigned int x, unsigned int y){ + + //get the number of pixels in the image + unsigned int pixels = x * y; + unsigned int bytes = pixels * sizeof(T); + + //allocate space on the GPU for the input image + T* gpuIn; + HANDLE_ERROR(cudaMalloc(&gpuIn, bytes)); + + //copy the image data to the GPU + HANDLE_ERROR(cudaMemcpy(gpuIn, in, bytes, cudaMemcpyHostToDevice)); + + //allocate space on the GPU for the output gradient image + T* gpuOut; + cudaMalloc(&gpuOut, bytes * 2); //the output image will have two channels (x, y) + + //call the GPU version of this function + gpu_gradient_2d(gpuOut, gpuIn, x, y); + + //copy the results to the CPU + cudaMemcpy(out, gpuOut, bytes * 2, cudaMemcpyDeviceToHost); + + //free allocated memory + cudaFree(gpuOut); + cudaFree(gpuIn); + } + + } +} + + +#endif \ No newline at end of file diff --git a/tira/math/filters/median2.cuh b/tira/math/filters/median2.cuh new file mode 100644 index 0000000..fa98906 --- /dev/null +++ b/tira/math/filters/median2.cuh @@ -0,0 +1,166 @@ +#ifndef STIM_CUDA_MEDIAN2_H +#define STIM_CUDA_MEDIAN2_H + +#include +#include +#include +#include + +#ifdef USING_CUDA +#include "cuda_runtime.h" +#include "device_launch_parameters.h" +#include +#endif + + +//#include +//#include +//#include +//#include + +template +__device__ void cuswap ( T& a, T& b ){ + T c(a); + a=b; + b=c; +} + +namespace stim{ + namespace cuda{ + + template + __global__ void cuda_median2(T* in, T* out, T* kernel, size_t X, size_t Y, size_t K){ + + int xi = blockIdx.x * blockDim.x + threadIdx.x; + int yi = blockIdx.y * blockDim.y + threadIdx.y; + + size_t Xout = X - K + 1; + size_t Yout = Y - K + 1; + int i = yi * Xout + xi; + + //return if (i,j) is outside the matrix + if(xi >= Xout || yi >= Yout) return; + + //scan the image, copy data from input to 2D window + size_t kxi, kyi; + for(kyi = 0; kyi < K; kyi++){ + for(kxi = 0; kxi < K; kxi++){ + kernel[i * K * K + kyi * K + kxi] = in[(yi + kyi)* X + xi + kxi]; + } + } + + //calculate kernel radius 4 + int r = (K*K)/2; + + //sort the smallest half pixel values inside the window, calculate the middle one + size_t Ki = i * K * K; + + //sort the smallest half pixel values inside the window, calculate the middle one + for (int p = 0; p < r+1; p++){ + for(int kk = p + 1; kk < K*K; kk++){ + if (kernel[Ki + kk] < kernel[Ki + p]){ + cuswap(kernel[Ki + kk], kernel[Ki + p]); + } + } + } + + //copy the middle pixel value inside the window to output + out[i] = kernel[Ki + r]; + + } + + + template + void gpu_median2(T* gpu_in, T* gpu_out, T* gpu_kernel, size_t X, size_t Y, size_t K){ + + //get the maximum number of threads per block for the CUDA device + int threads_total = stim::maxThreadsPerBlock(); + + //set threads in each block + dim3 threads(sqrt(threads_total), sqrt(threads_total)); + + //calculate the number of blocks + dim3 blocks(( (X - K + 1) / threads.x) + 1, ((Y - K + 1) / threads.y) + 1); + + //call the kernel to perform median filter function + cuda_median2 <<< blocks, threads >>>( gpu_in, gpu_out, gpu_kernel, X, Y, K); + + } + + template + void cpu_median2(T* cpu_in, T* cpu_out, size_t X, size_t Y, size_t K){ + + #ifndef USING_CUDA + + //output image width and height + size_t X_out = X + K -1; + size_t Y_out = Y + K -1; + + float* window = (float*)malloc(K * k *sizeof(float)); + + for(int i = 0; i< Y; i++){ + for(int j =0; j< X; j++){ + + //Pick up window elements + int k = 0; + for (int m=0; m< kernel_width; m++){ + for(int n = 0; n < kernel_width; n++){ + window[k] = in_image.at(m+i, n+j); + k++; + } + } + + //calculate kernel radius 4 + r_ker = K * K/2; + //Order elements (only half of them) + for(int p = 0; p(gpu_in, gpu_out, gpu_kernel, X, Y, K); + + //copy the array back to the CPU + HANDLE_ERROR( cudaMemcpy( cpu_out, gpu_out, N_out * sizeof(T), cudaMemcpyDeviceToHost) ); + + //free allocated memory + cudaFree(gpu_in); + cudaFree(gpu_kernel); + cudaFree(gpu_out); + + } + + } + #endif +} + + +#endif diff --git a/tira/math/filters/resample2.cuh b/tira/math/filters/resample2.cuh new file mode 100644 index 0000000..a3263ae --- /dev/null +++ b/tira/math/filters/resample2.cuh @@ -0,0 +1,16 @@ +#ifndef STIM_CUDA_RESAMPLE2_H +#define STIM_CUDA_RESAMPLE2_H + +#include +#include + + +///Downsamples a 2D image by a factor f using a box filter. Any pixels outside of the valid region +/// (for example if X%f != 0) are chopped. +template +void cpu_resample2(T* out, T* in, size_t f, size_t sx, size_t sy) { + +} + + +#endif \ No newline at end of file diff --git a/tira/math/filters/sepconv2.cuh b/tira/math/filters/sepconv2.cuh new file mode 100644 index 0000000..f733a76 --- /dev/null +++ b/tira/math/filters/sepconv2.cuh @@ -0,0 +1,89 @@ +#ifndef STIM_CUDA_SEPCONV2_H +#define STIM_CUDA_SEPCONV2_H +#include +#ifdef __CUDACC__ +#include +#include +#endif + +namespace stim { +#ifdef __CUDACC__ + //Performs a convolution of a 2D image using the GPU. All pointers are assumed to be to memory on the current device. + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void gpu_sepconv2(T* out, T* in, K* k0, K* k1, size_t sx, size_t sy, size_t kx, size_t ky) { + cudaDeviceProp p; + HANDLE_ERROR(cudaGetDeviceProperties(&p, 0)); + size_t tmax = p.maxThreadsPerBlock; + dim3 nt((unsigned int)sqrt(tmax), (unsigned int)sqrt(tmax)); //calculate the block dimensions + size_t X = sx - kx + 1; //calculate the x size of the output image + T* temp; //declare a temporary variable to store the intermediate image + HANDLE_ERROR(cudaMalloc(&temp, X * sy * sizeof(T))); //allocate memory for the intermediate image + + dim3 nb((unsigned int)(X / nt.x) + 1, (unsigned int)(sy / nt.y) + 1); //calculate the grid dimensions + size_t sm = (nt.x + kx - 1) * nt.y * sizeof(T); //shared memory bytes required to store block data + if (sm > p.sharedMemPerBlock) { + std::cout << "Error in stim::gpu_conv2() - insufficient shared memory for this kernel." << std::endl; + exit(1); + } + kernel_conv2 <<>> (temp, in, k0, sx, sy, kx, 1); //launch the kernel to compute the intermediate image + + size_t Y = sy - ky + 1; //calculate the y size of the output image + nb.y = (unsigned int)(Y / nt.y) + 1; //update the grid dimensions to reflect the Y-axis size of the output image + sm = nt.x * (nt.y + ky - 1) * sizeof(T); //calculate the amount of shared memory needed for the second pass + if (sm > p.sharedMemPerBlock) { + std::cout << "Error in stim::gpu_conv2() - insufficient shared memory for this kernel." << std::endl; + exit(1); + } + kernel_conv2 <<>> (out, temp, k1, X, sy, 1, ky); //launch the kernel to compute the final image + HANDLE_ERROR(cudaFree(temp)); //free memory allocated for the intermediate image + } +#endif + //Performs a separable convolution of a 2D image. Only valid pixels based on the kernel are returned. + // As a result, the output image will be smaller than the input image by (kx-1, ky-1) + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param k0 is the x-axis convolution filter + //@param k1 is the y-axis convolution filter + //@param sx is the size of the input image along X + //@param sy is the size of the input image along Y + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + template + void cpu_sepconv2(T* out, T* in, K* k0, K* k1, size_t sx, size_t sy, size_t kx, size_t ky) { + size_t X = sx - kx + 1; //x size of the output image + size_t Y = sy - ky + 1; +#ifdef __CUDACC__ + //allocate memory and copy everything to the GPU + T* gpu_in; + HANDLE_ERROR(cudaMalloc(&gpu_in, sx * sy * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpu_in, in, sx * sy * sizeof(T), cudaMemcpyHostToDevice)); + K* gpu_k0; + HANDLE_ERROR(cudaMalloc(&gpu_k0, kx * sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_k0, k0, kx * sizeof(K), cudaMemcpyHostToDevice)); + K* gpu_k1; + HANDLE_ERROR(cudaMalloc(&gpu_k1, ky * sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_k1, k1, ky * sizeof(K), cudaMemcpyHostToDevice)); + T* gpu_out; + HANDLE_ERROR(cudaMalloc(&gpu_out, X * Y * sizeof(T))); + gpu_sepconv2(gpu_out, gpu_in, gpu_k0, gpu_k1, sx, sy, kx, ky); //execute the GPU kernel + HANDLE_ERROR(cudaMemcpy(out, gpu_out, X * Y * sizeof(T), cudaMemcpyDeviceToHost)); //copy the result to the host + HANDLE_ERROR(cudaFree(gpu_in)); + HANDLE_ERROR(cudaFree(gpu_k0)); + HANDLE_ERROR(cudaFree(gpu_k1)); + HANDLE_ERROR(cudaFree(gpu_out)); +#else + T* temp = (T*)malloc(X * sy * sizeof(T)); //allocate space for the intermediate image + cpu_conv2(temp, in, k0, sx, sy, kx, 1); //evaluate the intermediate image + cpu_conv2(out, temp, k1, X, sy, 1, ky); //evaluate the final image + free(temp); //free the memory for the intermediate image +#endif + } +} + +#endif diff --git a/tira/math/filters/sepconv3.cuh b/tira/math/filters/sepconv3.cuh new file mode 100644 index 0000000..7250d26 --- /dev/null +++ b/tira/math/filters/sepconv3.cuh @@ -0,0 +1,123 @@ +#ifndef STIM_CUDA_SEPCONV3_H +#define STIM_CUDA_SEPCONV3_H + +#include +#include +#ifdef __CUDACC__ + #include + #include +#endif + +namespace stim +{ +#ifdef __CUDACC__ + template + void gpu_sepconv3(T* out, T* in, K* k0, K* k1, K* k2, size_t dimx, size_t dimy, size_t dimz, size_t kx, size_t ky, size_t kz) +{ + + size_t X = dimx - kx + 1; + size_t Y = dimy - ky + 1; + size_t Z = dimz - kz + 1; + + T* temp_out; + int idx_IN; + int idx_OUT; + HANDLE_ERROR(cudaMalloc(&temp_out, X*Y*dimz*sizeof(T))); + + for(int i = 0; i < dimz; i++) + { + idx_IN = (dimx*dimy)*i-i; + idx_OUT = (X*Y)*i-i; + gpu_sepconv2(&temp_out[idx_OUT], &in[idx_IN], k0, k1, dimx, dimy, kx, ky); + } + + cudaDeviceProp p; + HANDLE_ERROR(cudaGetDeviceProperties(&p, 0)); + size_t tmax = p.maxThreadsPerBlock; + + dim3 numThreads(sqrt(tmax), sqrt(tmax)); + dim3 numBlocks(X*Y/numThreads.x +1, dimz/numThreads.y + 1); + size_t sharedMem = (numThreads.x + kz - 1) * numThreads.y * sizeof(T); + if(sharedMem > p.sharedMemPerBlock) + { + std::cout << "Error in stim::gpu_sepconv3() - insufficient shared memory for this kernel." << std::endl; + exit(1); + } + kernel_conv2 <<< numBlocks, numThreads, sharedMem >>> (out, temp_out, k2, X*Y, dimz, 1, kz); + HANDLE_ERROR(cudaFree(temp_out)); + + +} +#endif + + //Performs a separable convolution of a 3D image. Only valid pixels based on the kernel ar e returned. + // As a result, the output image will be smaller than the input image by (kx-1, ky-1 , kz-1) + //@param out is a pointer to the output image + //@param in is a pointer to the input image + //@param kx is the x-axis convolution filter + //@param ky is the y-axis convolution filter + //@param kz is the z-axis convolution filter + //@param dimx is the size of the input image along X + //@param dimy is the size of the input image along Y + //@param dimz is the size of the input image along Z + //@param kx is the size of the kernel along X + //@param ky is the size of the kernel along Y + //@param kz is the size of the kernel along Z + + template + void cpu_sepconv3(T* out, T* in, K* k0, K* k1, K* k2, size_t dimx, size_t dimy, size_t dimz, size_t kx, size_t ky, size_t kz) + { + //Set up the sizes of the new output, which will be kx, ky, kz, smaller than the i nput. + size_t X = dimx - kx + 1; + size_t Y = dimy - ky + 1; + size_t Z = dimz - kz + 1; + +#ifdef __CUDACC__ + ///Set up all of the memory on the GPU + T* gpu_in; + HANDLE_ERROR(cudaMalloc(&gpu_in, dimx*dimy*dimz*sizeof(T))); + HANDLE_ERROR(cudaMemcpy(gpu_in, in, dimx*dimy*dimz*sizeof(T),cudaMemcpyHostToDevice)); + K* gpu_kx; + HANDLE_ERROR(cudaMalloc(&gpu_kx, kx*sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_kx, k0, kx*sizeof(K),cudaMemcpyHostToDevice)); + K* gpu_ky; + HANDLE_ERROR(cudaMalloc(&gpu_ky, ky*sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_ky, k1, ky*sizeof(K),cudaMemcpyHostToDevice)); + K* gpu_kz; + HANDLE_ERROR(cudaMalloc(&gpu_kz, kz*sizeof(K))); + HANDLE_ERROR(cudaMemcpy(gpu_kz, k2, kz*sizeof(K),cudaMemcpyHostToDevice)); + T* gpu_out; + HANDLE_ERROR(cudaMalloc(&gpu_out, X * Y * Z*sizeof(T))); + + ///run the kernel + gpu_sepconv3(gpu_out, gpu_in, gpu_kx, gpu_ky, gpu_kz, dimx, dimy, dimz, kx, ky, kz); + + ///Copy the output + HANDLE_ERROR(cudaMemcpy(out, gpu_out, X*Y*Z*sizeof(T), cudaMemcpyDeviceToHost)); + + ///Free all the memory used. + HANDLE_ERROR(cudaFree(gpu_in)); + HANDLE_ERROR(cudaFree(gpu_kx)); + HANDLE_ERROR(cudaFree(gpu_ky)); + HANDLE_ERROR(cudaFree(gpu_kz)); + HANDLE_ERROR(cudaFree(gpu_out)); +#else + T* temp = (T*) malloc(X * dimy * sizeof(T)); + T* temp3 = (T*) malloc(X * Y * dimz * sizeof(T)); + for(int i = 0; i < dimz; i++) + { + idx_IN = (dimx*dimy)*i-i; + idx_OUT = (X*Y)*i-i; + cpu_conv2(temp, &in[idx_IN], k0, dimx, dimy, kx, 1) + cpu_conv2(&temp3[idx_OUT], temp, k1, X, dimy, 1, ky); + } + cpu_conv2(out, temp, k2, X*Y, dimz, 1, kz); + free(temp); + free(temp3); + +#endif + } +} + + +#endif diff --git a/tira/math/function.h b/tira/math/function.h new file mode 100644 index 0000000..c04ca15 --- /dev/null +++ b/tira/math/function.h @@ -0,0 +1,236 @@ +#ifndef RTS_FUNCTION_H +#define RTS_FUNCTION_H + +#include + +namespace stim{ + +//template class for a one-dimensional function +template +class function +{ +protected: + + std::vector X; + std::vector Y; + Ty range[2]; + Ty bounding[2]; + + //comparison function for searching lambda + static bool findCeiling(Tx ax, Tx bx){ + return (ax > bx); + } + + //process a string to extract a function (generally delimited by tabs, commas, spaces, etc.) + void process_string(std::string s){ + std::stringstream ss(s); + + Tx x; + Ty y; + std::string line; + + while(!ss.eof()){ + + std::getline(ss, line); + if(line[0] == '#') continue; + + std::stringstream lstream(line); + + lstream>>x; //read the x value + lstream>>y; //read the y value + + if(ss.eof()) break; + insert(x, y); //insert the read value into the function + } + } + + void update_range(Ty y){ + //if the function is empty, just set the range to the given value + if(X.size() == 0){ + range[0] = range[1] = y; + } + + //otherwise compare the values and set them appropriately + if(y < range[0]) range[0] = y; + if(y > range[1]) range[1] = y; + } + + +public: + function(){ + range[0] = range[1] = 0; + bounding[0] = bounding[1] = 0; + } + + //linear interpolation + Ty linear(Tx x) const + { + if(X.size() == 0) return bounding[0]; //return zero if the function is empty + //return bounding values if x is outside the sample domain + if(x < X[0]) return bounding[0]; + if(x > X.back()) return bounding[1]; + + unsigned int N = X.size(); //number of sample points + + //declare an iterator + typedef typename std::vector< Tx >::iterator f_iter; //declare an iterator + f_iter it; + + //find the first X-coordinate that is greater than x + unsigned int i; + for(i = 0; i x) + break; + } + //i currently holds the ceiling + + //if the wavelength is past the end of the list, return the last sample point + if(i == N) return Y.back(); + //if the wavelength is before the beginning of the list, return the front + else if(i == 0) return Y[0]; + //otherwise interpolate + else{ + Tx xMax = X[i]; + Tx xMin = X[i-1]; + + Tx a = (x - xMin) / (xMax - xMin); + Ty riMin = Y[i - 1]; + Ty riMax = Y[i]; + Ty interp = riMax * a + riMin * (1 - a); + return interp; + } + } + + ///add a data point to a function + void insert(Tx x, Ty y) + { + unsigned int N = X.size(); //number of sample points + + update_range(y); //update the range of the function + + if(N == 0 || X[N-1] < x){ + X.push_back(x); + Y.push_back(y); + return; + } + + //declare an iterator and search for the x value + typename std::vector< Tx >::iterator it; + it = search(X.begin(), X.end(), &x, &x + 1, &function::findCeiling); + + //if the function value is past the end of the vector, add it to the back + if(*it == N){ + X.push_back(x); + Y.push_back(y); + } + //otherwise add the value at the iterator position + else{ + X.insert(it, x); + Y.insert(Y.begin() + *it, y); + } + + } + + Tx getX(unsigned int i) const{ + return X[i]; + } + + Ty getY(unsigned int i) const{ + return Y[i]; + } + + ///get the number of data points in the function + unsigned int getN() const{ + return X.size(); + } + + //look up an indexed component + Ty operator[](int i) const{ + if(i <= X.size()){ + std::cout<<"ERROR: accessing non-existing sample point in 'function'"< operator+(Ty r) const{ + + function result; + + //if there are points in the function + if(X.size() > 0){ + //add r to every point in f + for(unsigned int i=0; i & operator= (const Ty & rhs){ + X.clear(); + Y.clear(); + bounding[0] = bounding[1] = rhs; //set the boundary values to rhs + if(rhs != 0) //if the RHS is zero, just clear, otherwise add one value of RHS + insert(0, rhs); + + return *this; + } + + + //output a string description of the function (used for console output and debugging) + std::string str(){ + unsigned int N = X.size(); + + std::stringstream ss; + ss<<"# of samples: "<(t)), + std::istreambuf_iterator()); + + process_string(str); + } + + +}; + +} //end namespace rts + + +#endif diff --git a/tira/math/legendre.h b/tira/math/legendre.h new file mode 100644 index 0000000..1099d4e --- /dev/null +++ b/tira/math/legendre.h @@ -0,0 +1,47 @@ +#ifndef RTS_LEGENDRE_H +#define RTS_LEGENDRE_H + +#include "../cuda/cudatools/callable.h" + +namespace stim{ + +template +CUDA_CALLABLE void init_legendre(T x, T& P0, T& P1) +{ + //compute the first two Legendre polynomials + P0 = 1; + P1 = x; +} + +template +CUDA_CALLABLE void shift_legendre(int n, T x, T& P0, T& P1) +{ + //compute the next (order n) Legendre polynomial + T Pnew = ( (2 * n - 1) * x * P1 - (n-1) * P0 ) / n; + + //shift and add the new value to the array + P0 = P1; + P1 = Pnew; +} + +/// Iteratively evaluates the Legendre polynomials for orders l = [0 n] +template +CUDA_CALLABLE void legendre(int n, T x, T* P) +{ + if(n < 0) return; + P[0] = 1; + + if(n >= 1) + P[1] = x; + + for(int i=2; i<=n; i++) + { + P[i] = ( (2 * i - 1) * x * P[i-1] - (i-1) * P[i-2] ) / i; + } + +} + +} + + +#endif diff --git a/tira/math/matrix.h b/tira/math/matrix.h new file mode 100644 index 0000000..aa97db5 --- /dev/null +++ b/tira/math/matrix.h @@ -0,0 +1,509 @@ +#ifndef STIM_MATRIX_H +#define STIM_MATRIX_H + +//#include "rts/vector.h" +#include +#include +#include +#include +#include +//#include + +namespace stim{ + + enum mat4Format { + mat4_float64, + mat4_float32, + mat4_int32, + mat4_int16, + mat4_uint16, + mat4_uint8, + mat4_float //floating point type, determined automatically + }; + + static size_t mat4Format_size(mat4Format f){ + switch(f){ + case mat4_float64: return 8; + case mat4_float32: + case mat4_int32: return 4; + case mat4_int16: + case mat4_uint16: return 2; + case mat4_uint8: return 1; + default: return 0; + } + } + + //class encapsulates a mat4 file, and can be used to write multiple matrices to a single mat4 file + class mat4file { + std::ofstream matfile; + + public: + /// Constructor opens a mat4 file for writing + mat4file(std::string filename) { + matfile.open(filename.c_str(), std::ios::binary); + } + + bool is_open() { + return matfile.is_open(); + } + + void close() { + matfile.close(); + } + + bool writemat(char* data, std::string varname, size_t sx, size_t sy, mat4Format format) { + //save the matrix file here (use the mat4 function above) + //data format: https://maxwell.ict.griffith.edu.au/spl/matlab-page/matfile_format.pdf (page 32) + + int MOPT = 0; //initialize the MOPT type value to zero + int m = 0; //little endian + int o = 0; //reserved, always 0 + int p = format; + int t = 0; + MOPT = m * 1000 + o * 100 + p * 10 + t; //calculate the type value + int mrows = (int)sx; + int ncols = (int)sy; + int imagf = 0; //assume real (for now) + varname.push_back('\0'); //add a null to the string + int namlen = (int)varname.size(); //calculate the name size + + size_t bytes = sx * sy * mat4Format_size(format); + matfile.write((char*)&MOPT, 4); + matfile.write((char*)&mrows, 4); + matfile.write((char*)&ncols, 4); + matfile.write((char*)&imagf, 4); + matfile.write((char*)&namlen, 4); + matfile.write((char*)&varname[0], namlen); + matfile.write((char*)data, bytes); //write the matrix data + return is_open(); + } + }; + + static void save_mat4(char* data, std::string filename, std::string varname, size_t sx, size_t sy, mat4Format format){ + mat4file outfile(filename); //create a mat4 file object + if (outfile.is_open()) { //if the file is open + outfile.writemat(data, varname, sx, sy, format); //write the matrix + outfile.close(); //close the file + } + } + +template +class matrix { + //the matrix will be stored in column-major order (compatible with OpenGL) + T* M; //pointer to the matrix data + size_t R; //number of rows + size_t C; //number of colums + + size_t bytes() { + return R * C * sizeof(T); //return the number of bytes of matrix data + } + /*void init(size_t rows, size_t cols){ + R = rows; + C = cols; + if (R == 0 || C == 0) M = NULL; + else + M = (T*)malloc(R * C * sizeof(T)); //allocate space for the matrix + }*/ + + T get(const size_t row, const size_t col) const { + if (row >= R || col >= C) { + std::cout << "ERROR: row or column out of range." << std::endl; + exit(1); + } + return M[col * R + row]; + } + + T& at(size_t row, size_t col){ + if (row >= R || col >= C) { + std::cout << "ERROR: row or column out of range." << std::endl; + exit(1); + } + return M[col * R + row]; + } + +public: + matrix() { + R = 0; + C = 0; + M = NULL; + } + + matrix(size_t rows, size_t cols) { + R = rows; + C = cols; + M = NULL; + if (R * C > 0) + M = (T*) malloc(R * C * sizeof(T)); + } + + matrix(size_t rows, size_t cols, const T* data) { + R = rows; + C = cols; + M = NULL; + if (R * C > 0) + M = (T*)malloc(R * C * sizeof(T)); + memcpy(M, data, R * C * sizeof(T)); + } + + matrix(const matrix& cpy){ + M = NULL; + if (cpy.R * cpy.C > 0) + M = (T*)malloc(cpy.R * cpy.C * sizeof(T)); + memcpy(M, cpy.M, cpy.R * cpy.C * sizeof(T)); + + R = cpy.R; + C = cpy.C; + } + + ~matrix() { + if(M) free(M); + M = NULL; + R = C = 0; + } + + size_t rows() const { + return R; + } + + size_t cols() const { + return C; + } + + T& operator()(size_t row, size_t col) { + return at(row, col); + } + + matrix& operator=(const T rhs) { + //init(R, C); + size_t N = R * C; + for(size_t n=0; n& operator=(const matrix& rhs){ + if (this != &rhs) { //if the matrix isn't self-assigned + T* new_matrix = new T[rhs.R * rhs.C]; //allocate new resources + memcpy(new_matrix, rhs.M, rhs.R * rhs.C * sizeof(T)); //copy the matrix + + delete[] M; //delete the previous array + M = new_matrix; + R = rhs.R; + C = rhs.C; + } + return *this; + } + + //element-wise operations + matrix operator+(const T rhs) const { + matrix result(R, C); //create a result matrix + size_t N = R * C; + + for(int i=0; i operator+(const matrix rhs) const { + if (R != rhs.R || C != rhs.C) { + std::cout << "ERROR: addition is only defined for matrices that are the same size." << std::endl; + exit(1); + } + matrix result(R, C); //create a result matrix + size_t N = R * C; + + for (int i = 0; i < N; i++) + result.M[i] = M[i] + rhs.M[i]; //calculate the operation and assign to result + + return result; + } + + matrix operator-(const T rhs) const { + return operator+(-rhs); //add the negative of rhs + } + + matrix operator-(const matrix rhs) const { + return operator+(-rhs); + } + + matrix operator-() const { + matrix result(R, C); //create a result matrix + size_t N = R * C; + + for (int i = 0; i < N; i++) + result.M[i] = -M[i]; //calculate the operation and assign to result + + return result; + } + + matrix operator*(const T rhs) const { + matrix result(R, C); //create a result matrix + size_t N = R * C; + + for(int i=0; i operator/(const T rhs) const { + matrix result(R, C); //create a result matrix + size_t N = R * C; + + for(int i=0; i operator*(const matrix rhs) const { + if(C != rhs.R){ + std::cout<<"ERROR: matrix multiplication is undefined for matrices of size "; + std::cout<<"[ "< result(R, rhs.C); //create the output matrix + T inner; //stores the running inner product + size_t c, r, i; + for(c = 0; c < rhs.C; c++){ + for(r = 0; r < R; r++){ + inner = (T)0; + for(i = 0; i < C; i++){ + inner += get(r, i) * rhs.get(i, c); + } + result.M[c * R + r] = inner; + } + } + return result; + } + + //returns a pointer to the raw matrix data (in column major format) + T* data(){ + return M; + } + + //return a transposed matrix + matrix transpose() const { + matrix result(C, R); + size_t c, r; + for(c = 0; c < C; c++){ + for(r = 0; r < R; r++){ + result.M[r * C + c] = M[c * R + r]; + } + } + return result; + } + + // Reshapes the matrix in place + void reshape(size_t rows, size_t cols) { + R = rows; + C = cols; + } + + ///Calculate and return the determinant of the matrix + T det() const { + if (R != C) { + std::cout << "ERROR: a determinant can only be calculated for a square matrix." << std::endl; + exit(1); + } + if (R == 1) return M[0]; //if the matrix only contains one value, return it + + int r, c, ri, cia, cib; + T a = 0; + T b = 0; + for (c = 0; c < (int)C; c++) { + for (r = 0; r < R; r++) { + ri = r; + cia = (r + c) % (int)C; + cib = ((int)C - 1 - r) % (int)C; + a += get(ri, cia); + b += get(ri, cib); + } + } + return a - b; + } + + /// Sum all elements in the matrix + T sum() const { + size_t N = R * C; //calculate the number of elements in the matrix + T s = (T)0; //allocate a register to store the sum + for (size_t n = 0; n < N; n++) s += M[n]; //perform the summation + return s; + } + + /// Sort rows of the matrix by the specified indices + matrix sort_rows(size_t* idx) const { + matrix result(C, R); //create the output matrix + size_t r, c; + for (c = 0; c < C; c++) { //for each column + for (r = 0; r < R; r++) { //for each row element + result.M[c * R + r] = M[c * R + idx[r]]; //copy each element of the row into its new position + } + } + return result; + } + + /// Sort columns of the matrix by the specified indices + matrix sort_cols(size_t* idx, size_t data_type = mat4_float) const { + matrix result(C, R); + size_t c; + for (c = 0; c < C; c++) { //for each column + memcpy(&result.M[c * R], &M[idx[c] * R], sizeof(T) * R); //copy the entire column from this matrix to the appropriate location + } + return result; + } + + /// Return the column specified by index i + matrix col(size_t i) { + matrix c(R, 1); //create a single column matrix + memcpy(c.data(), &data()[R*i], C * sizeof(T)); //copy the column + return c; + } + + /// Return the row specified by index i + matrix row(size_t i) { + matrix r(1, C); //create a single row matrix + for (size_t c = 0; c < C; c++) + r(0, c) = at(i, c); + return r; + } + + std::string toStr() const { + std::stringstream ss; + + for(int r = 0; r < R; r++) { + ss << "| "; + for(int c=0; c::max_digits10; + csvss.precision(digits); + csv(csvss); + return csvss.str(); + } + + + + //save the data as a CSV file + void csv(std::string filename) const { + std::ofstream basisfile(filename.c_str()); + basisfile << csv(); + basisfile.close(); + } + + static matrix I(size_t N) { + matrix result(N, N); //create the identity matrix + memset(result.M, 0, N * N * sizeof(T)); //set the entire matrix to zero + for (size_t n = 0; n < N; n++) { + result(n, n) = (T)1; //set the diagonal component to 1 + } + return result; + } + + //loads a matrix from a stream in CSV format + void csv(std::istream& in) { + size_t c, r; + T v; + for (r = 0; r < R; r++) { + for (c = 0; c < C; c++) { + in >> v; + if (in.peek() == ',') in.seekg(1, std::ios::cur); + at(r, c) = v;; + } + } + } + + void raw(std::string filename) { + std::ofstream out(filename, std::ios::binary); + if (out) { + out.write((char*)data(), rows() * cols() * sizeof(T)); + out.close(); + } + } + + void mat4(stim::mat4file& file, std::string name = std::string("unknown"), mat4Format format = mat4_float) { + //make sure the matrix name is valid (only numbers and letters, with a letter at the beginning + for (size_t c = 0; c < name.size(); c++) { + if (name[c] < 48 || //if the character isn't a number or letter, replace it with '_' + (name[c] > 57 && name[c] < 65) || + (name[c] > 90 && name[c] < 97) || + (name[c] > 122)) { + name[c] = '_'; + } + } + if (name[0] < 65 || + (name[0] > 91 && name[0] < 97) || + name[0] > 122) { + name = std::string("m") + name; + } + if (format == mat4_float) { + if (sizeof(T) == 4) format = mat4_float32; + else if (sizeof(T) == 8) format = mat4_float64; + else { + std::cout << "stim::matrix ERROR - incorrect format specified" << std::endl; + exit(1); + } + } + //the name is now valid + + //if the size of the array is more than 100,000,000 elements, the matrix isn't supported + if (rows() * cols() > 100000000) { //break the matrix up into multiple parts + //mat4file out(filename); //create a mat4 object to write the matrix + if (file.is_open()) { + if (rows() < 100000000) { //if the size of the row is less than 100,000,000, split the matrix up by columns + size_t ncols = 100000000 / rows(); //calculate the number of columns that can fit in one matrix + size_t nmat = (size_t)std::ceil((double)cols() / (double)ncols); //calculate the number of matrices required + for (size_t m = 0; m < nmat; m++) { //for each matrix + std::stringstream ss; + ss << name << "_part_" << m + 1; + if (m == nmat - 1) + file.writemat((char*)(data() + m * ncols * rows()), ss.str(), rows(), cols() - m * ncols, format); + else + file.writemat((char*)(data() + m * ncols * rows()), ss.str(), rows(), ncols, format); + } + } + } + } + //call the mat4 subroutine + else + //stim::save_mat4((char*)M, filename, name, rows(), cols(), format); + file.writemat((char*)data(), name, rows(), cols(), format); + } + + // saves the matrix as a Level-4 MATLAB file + void mat4(std::string filename, std::string name = std::string("unknown"), mat4Format format = mat4_float) { + stim::mat4file matfile(filename); + + if (matfile.is_open()) { + mat4(matfile, name, format); + matfile.close(); + } + } +}; + +} //end namespace rts + + +#endif diff --git a/tira/math/matrix_sq.h b/tira/math/matrix_sq.h new file mode 100644 index 0000000..3c642d0 --- /dev/null +++ b/tira/math/matrix_sq.h @@ -0,0 +1,165 @@ +#ifndef STIM_MATRIX_SQ_H +#define STIM_MATRIX_SQ_H + +//#include "rts/vector.h" +#include +#include +#include +#include +#include + +namespace tira { + +template +struct matrix_sq +{ + //the matrix will be stored in column-major order (compatible with OpenGL) + T M[N*N]; + + CUDA_CALLABLE matrix_sq() + { + for(int r=0; r set(T rhs[N*N]) + { + memcpy(M, rhs, sizeof(T)*N*N); + return *this; + } + + //create a symmetric matrix given the rhs values, given in column-major order + CUDA_CALLABLE void setsym(T rhs[(N*N+N)/2]){ + const size_t L = (N*N+N)/2; //store the number of values + + size_t r, c; + r = c = 0; + for(size_t i = 0; i < L; i++){ //for each value + if(r == c) M[c * N + r] = rhs[i]; + else M[c*N + r] = M[r * N + c] = rhs[i]; + r++; + if(r == N) r = ++c; + } + } + + CUDA_CALLABLE T& operator()(int row, int col) + { + return M[col * N + row]; + } + + CUDA_CALLABLE matrix_sq operator=(T rhs) + { + int Nsq = N*N; + for(int i=0; i operator-(T rhs) + { + for(int i=0; i operator/(T rhs) + { + for(int i=0; i + vec operator*(vec rhs){ + unsigned int M = rhs.size(); + + vec result; + result.resize(M); + + for(int r=0; r + CUDA_CALLABLE vec3 operator*(vec3 rhs){ + vec3 result(0, 0, 0); + for(int r=0; r<3; r++) + for(int c=0; c<3; c++) + result[r] += (*this)(r, c) * rhs[c]; + + return result; + } + + std::string toStr() + { + std::stringstream ss; + + for(int r = 0; r < N; r++) + { + ss << "| "; + for(int c=0; c identity() { + matrix_sq I; + I = 0; + for (size_t i = 0; i < N; i++) + I.M[i * N + i] = 1; + return I; + } +}; + +} //end namespace rts + +template +std::ostream& operator<<(std::ostream& os, tira::matrix_sq M) +{ + os< 3 && __GNUC_MINOR__ > 7 +//template using rtsMatrix = rts::matrix; +//#endif + +#endif diff --git a/tira/math/matrix_sym.h b/tira/math/matrix_sym.h new file mode 100644 index 0000000..8761c43 --- /dev/null +++ b/tira/math/matrix_sym.h @@ -0,0 +1,118 @@ +#ifndef STIM_MATRIX_SYM_H +#define STIM_MATRIX_SYM_H + +#include +#include + +/* This class represents a rank 2, 3-dimensional tensor viable +for representing tensor fields such as structure and diffusion tensors +*/ +namespace stim{ + +template +class matrix_sym{ + +protected: + //values are stored in column-major order as a lower-triangular matrix + T M[D*(D + 1)/2]; + + static size_t idx(size_t r, size_t c) { + //if the index is in the upper-triangular portion, swap the indices + if(r < c){ + size_t t = r; + r = c; + c = t; + } + + size_t ci = (c + 1) * (D + (D - c))/2 - 1; //index to the end of column c + size_t i = ci - (D - r - 1); + return i; + } + + //calculate the row and column given an index + //static void indices(size_t& r, size_t& c, size_t idx) { + // size_t col = 0; + // for ( ; col < D; col++) + // if(idx <= ((D - col + D) * (col + 1)/2 - 1)) + // break; + + // c = col; + // size_t ci = (D - (col - 1) + D) * col / 2 - 1; //index to the end of last column col -1 + // r = idx - ci + c - 1; + //} + static void indices(size_t& r, size_t& c, size_t idx) { + size_t cf = -1/2 * sqrt(4 * D * D + 4 * D - (7 + 8 * idx)) + D - 1/2; + c = ceil(cf); + r = idx - D * c + c * (c + 1) / 2; + } + +public: + //return the symmetric matrix associated with this tensor + stim::matrix mat() { + stim::matrix r; + r.setsym(M); + return r; + } + + CUDA_CALLABLE T& operator()(int r, int c) { + return M[idx(r, c)]; + } + + CUDA_CALLABLE matrix_sym operator=(T rhs) { + int Nsq = D*(D+1)/2; + for(int i=0; i operator=(matrix_sym rhs) { + size_t N = D * (D + 1) / 2; + for (size_t i = 0; i < N; i++) M[i] = rhs.M[i]; + return *this; + } + + CUDA_CALLABLE T trace() { + T tr = 0; + for (size_t i = 0; i < D; i++) //for each diagonal value + tr += M[idx(i, i)]; //add the value on the diagonal + return tr; + } + // overload matrix multiply scalar + CUDA_CALLABLE void operator_product(matrix_sym &B, T rhs) { + int Nsq = D*(D+1)/2; + for(int i=0; i identity() { + matrix_sym I; + I = 0; + for (size_t i = 0; i < D; i++) + I.M[matrix_sym::idx(i, i)] = 1; + return I; + } +}; + + + +} //end namespace stim + + +#endif diff --git a/tira/math/meshgrid.h b/tira/math/meshgrid.h new file mode 100644 index 0000000..acfa8bb --- /dev/null +++ b/tira/math/meshgrid.h @@ -0,0 +1,42 @@ +#ifndef STIM_MESHGRID_H +#define STIM_MESHGRID_H + +namespace stim{ + + /// Create a 2D grid based on a pair of vectors representing the grid spacing (see Matlab) + /// @param X is an [nx x ny] array that will store the X coordinates for each 2D point + /// @param Y is an [nx x ny] array that will store the Y coordinates for each 2D point + /// @param x is an [nx] array that provides the positions of grid points in the x direction + /// @param nx is the number of grid points in the x direction + /// @param y is an [ny] array that provides the positions of grid points in the y direction + /// @param ny is the number of grid points in the y direction + template + void meshgrid(T* X, T* Y, T* x, size_t nx, T* y, size_t ny){ + size_t xi, yi; //allocate index variables + for(yi = 0; yi < ny; yi++){ //iterate through each column + for(xi = 0; xi < nx; xi++){ //iterate through each row + X[yi * nx + xi] = x[xi]; + Y[yi * nx + xi] = y[yi]; + } + } + } + + /// Creates an array of n equally spaced values in the range [xmin xmax] + /// @param X is an array of length n that stores the values + /// @param xmin is the start point of the array + /// @param xmax is the end point of the array + /// @param n is the number of points in the array + template + void linspace(T* X, T xmin, T xmax, size_t n){ + T alpha; + for(size_t i = 0; i < n; i++){ + alpha = (T)i / (T)n; + X[i] = (1 - alpha) * xmin + alpha * xmax; + } + } + + +} + + +#endif \ No newline at end of file diff --git a/tira/math/plane.h b/tira/math/plane.h new file mode 100644 index 0000000..88ef37a --- /dev/null +++ b/tira/math/plane.h @@ -0,0 +1,224 @@ +#ifndef TIRA_PLANE_H +#define TIRA_PLANE_H + +#include +#include +#include +#include + + +namespace tira +{ +template class plane; +} + +template +CUDA_CALLABLE tira::plane operator-(tira::plane v); + +namespace tira +{ + +template +class plane +{ + protected: + vec3 P; + vec3 N; + vec3 U; + + ///Initializes the plane with standard coordinates. + /// + CUDA_CALLABLE void init() + { + P = vec3(0, 0, 0); + N = vec3(0, 0, 1); + U = vec3(1, 0, 0); + } + + public: + + CUDA_CALLABLE plane() + { + init(); + } + + CUDA_CALLABLE plane(vec3 n, vec3 p = vec3(0, 0, 0)) + { + init(); + P = p; + rotate(n.norm()); + } + + CUDA_CALLABLE plane(T z_pos) + { + init(); + P[2] = z_pos; + } + + //create a plane from three points (a triangle) + CUDA_CALLABLE plane(vec3 a, vec3 b, vec3 c) + { + init(); + P = c; + vec3 n = (c - a).cross(b - a); + try + { + if(n.len() != 0) + { + rotate(n.norm()); + } else { + throw 42; + } + } + catch(int i) + { + std::cerr << "No plane can be creates as all points a,b,c lie on a straight line" << std::endl; + } + } + + template< typename U > + CUDA_CALLABLE operator plane() + { + plane result(N, P); + return result; + + } + + CUDA_CALLABLE vec3 n() + { + return N; + } + + CUDA_CALLABLE vec3 p() + { + return P; + } + + CUDA_CALLABLE vec3 u() + { + return U; + } + + ///flip the plane front-to-back + CUDA_CALLABLE plane flip(){ + plane result = *this; + result.N = -result.N; + return result; + } + + //determines how a vector v intersects the plane (1 = intersects front, 0 = within plane, -1 = intersects back) + CUDA_CALLABLE int face(vec3 v){ + + T dprod = v.dot(N); //get the dot product between v and N + + //conditional returns the appropriate value + if(dprod < 0) + return 1; + else if(dprod > 0) + return -1; + else + return 0; + } + + //determine on which side of the plane a point lies (1 = front, 0 = on the plane, -1 = bac k) + CUDA_CALLABLE int side(vec3 p){ + + vec3 v = p - P; //get the vector from P to the query point p + + return face(v); + } + + //compute the component of v that is perpendicular to the plane + CUDA_CALLABLE vec3 perpendicular(vec3 v){ + return N * v.dot(N); + } + + //compute the projection of v in the plane + CUDA_CALLABLE vec3 parallel(vec3 v){ + return v - perpendicular(v); + } + + CUDA_CALLABLE void setU(vec3 v) + { + U = (parallel(v.norm())).norm(); + } + + CUDA_CALLABLE void decompose(vec3 v, vec3& para, vec3& perp){ + perp = N * v.dot(N); + para = v - perp; + } + + //get both the parallel and perpendicular components of a vector v w.r.t. the plane + CUDA_CALLABLE void project(vec3 v, vec3 &v_par, vec3 &v_perp){ + + v_perp = v.dot(N); + v_par = v - v_perp; + } + + //compute the reflection of v off of the plane + CUDA_CALLABLE vec3 reflect(vec3 v){ + + //compute the reflection using N_prime as the plane normal + vec3 par = parallel(v); + vec3 r = (-v) + par * 2; + return r; + + } + + CUDA_CALLABLE plane operator-() + { + plane p = *this; + + //negate the normal vector + p.N = -p.N; + return p; + } + + //output a string + std::string str(){ + std::stringstream ss; + ss<<"P: "< n) + { + quaternion q; + q.CreateRotation(N, n); + matrix_sq M = q.toMatrix3(); + N = M * N; + U = M * U; + + } + + CUDA_CALLABLE void rotate(vec3 n, vec3 &Y) + { + quaternion q; + q.CreateRotation(N, n); + + N = q.toMatrix3() * N; + U = q.toMatrix3() * U; + Y = q.toMatrix3() * Y; + + } + + CUDA_CALLABLE void rotate(vec3 n, vec3 &X, vec3 &Y) + { + quaternion q; + q.CreateRotation(N, n); + + N = q.toMatrix3() * N; + U = q.toMatrix3() * U; + X = q.toMatrix3() * X; + Y = q.toMatrix3() * Y; + + } + +}; + + +} +#endif diff --git a/tira/math/quaternion.h b/tira/math/quaternion.h new file mode 100644 index 0000000..8a8da32 --- /dev/null +++ b/tira/math/quaternion.h @@ -0,0 +1,208 @@ +#ifndef TIRA_QUATERNION_H +#define TIRA_QUATERNION_H + +#include +#include + +namespace tira { + +template +class quaternion +{ +public: + T w; + T x; + T y; + T z; + + CUDA_CALLABLE void normalize(){ + + double length=sqrt(w*w + x*x + y*y + z*z); + w=w/length; + x=x/length; + y=y/length; + z=z/length; + } + + //calculate the quaternion length (norm) + CUDA_CALLABLE T norm() { + return sqrt(w*w + x * x + y * y + z * z); + } + + CUDA_CALLABLE void CreateRotation(T theta, T ux, T uy, T uz){ + + vec3 u(ux, uy, uz); + CreateRotation(theta, u); + } + + CUDA_CALLABLE void CreateRotation(T theta, vec3 u){ + + vec3 u_hat = u.norm(); + + //assign the given Euler rotation to this quaternion + w = (T)cos(theta/2); + x = u_hat[0]*(T)sin(theta/2); + y = u_hat[1]*(T)sin(theta/2); + z = u_hat[2]*(T)sin(theta/2); + } + + CUDA_CALLABLE void CreateRotation(vec3 from, vec3 to){ + + from = from.norm(); + to = to.norm(); + vec3 r = from.cross(to); //compute the rotation vector + //T l = r.len(); + //if (l > 1) l = 1; //we have seen degenerate cases where |r| > 1 (probably due to loss of precision in the cross product) + //T theta = asin(l); //compute the angle of the rotation about r + //deal with a zero vector (both k and kn point in the same direction) + T cos_theta = from.dot(to); //calculate the cosine between the two vectors + T theta = acos(cos_theta); //calculate the angle between the two vectors + if(theta == (T)0){ + return; + } + + //create a quaternion to capture the rotation + CreateRotation(theta, r.norm()); + } + + + + CUDA_CALLABLE quaternion operator *(quaternion ¶m){ + + float A, B, C, D, E, F, G, H; + + + A = (w + x)*(param.w + param.x); + B = (z - y)*(param.y - param.z); + C = (w - x)*(param.y + param.z); + D = (y + z)*(param.w - param.x); + E = (x + z)*(param.x + param.y); + F = (x - z)*(param.x - param.y); + G = (w + y)*(param.w - param.z); + H = (w - y)*(param.w + param.z); + + quaternion result; + result.w = B + (-E - F + G + H) /2; + result.x = A - (E + F + G + H)/2; + result.y = C + (E - F + G - H)/2; + result.z = D + (E - F - G + H)/2; + + return result; + } + + CUDA_CALLABLE matrix_sq toMatrix3(){ + + matrix_sq result; + + + T wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; + + + // calculate coefficients + x2 = x + x; y2 = y + y; + z2 = z + z; + xx = x * x2; xy = x * y2; xz = x * z2; + yy = y * y2; yz = y * z2; zz = z * z2; + wx = w * x2; wy = w * y2; wz = w * z2; + + result(0, 0) = 1 - (yy + zz); + result(0, 1) = xy - wz; + + result(0, 2) = xz + wy; + + result(1, 0) = xy + wz; + result(1, 1) = 1 - (xx + zz); + + result(1, 2) = yz - wx; + + result(2, 0) = xz - wy; + result(2, 1) = yz + wx; + + result(2, 2) = 1 - (xx + yy); + + return result; + } + + CUDA_CALLABLE matrix_sq toMatrix4(){ + matrix_sq R; + T s, wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; + s = sqrt(norm()); + xx = x * x; xy = x * y; xz = x * z; + yy = y * y; yz = y * z; + zz = z * z; + wx = w * x; wy = w * y; wz = w * z; + + R(0, 0) = 1 - 2 * s * (yy + zz); + R(0, 1) = 2 * s * (xy - wz); + R(0, 2) = 2 * s * (xz + wy); + R(1, 0) = 2 * s * (xy + wz); + R(1, 1) = 1 - 2 * s * (xx + zz); + R(1, 2) = 2 * s * (yz - wx); + R(2, 0) = 2 * s * (xz - wy); + R(2, 1) = 2 * s * (yz + wx); + R(2, 2) = 1 - 2 * s * (xx + yy); + + R(0, 3) = 0; + R(1, 3) = 0; + R(2, 3) = 0; + R(3, 0) = 0; + R(3, 1) = 0; + R(3, 2) = 0; + R(3, 3) = 1; + + // calculate coefficients + /*x2 = x + x; y2 = y + y; + z2 = z + z; + xx = x * x2; xy = x * y2; xz = x * z2; + yy = y * y2; yz = y * z2; zz = z * z2; + wx = w * x2; wy = w * y2; wz = w * z2; + + result(0, 0) = 1 - (yy + zz); + result(0, 1) = xy - wz; + + result(0, 2) = xz + wy; + + result(1, 0) = xy + wz; + result(1, 1) = 1 - (xx + zz); + + result(1, 2) = yz - wx; + + result(2, 0) = xz - wy; + result(2, 1) = yz + wx; + + result(2, 2) = 1 - (xx + yy); + + result(3, 3) = 1;*/ + + return R; + } + + + CUDA_CALLABLE quaternion(){ + w=0; x=0; y=0; z=0; + } + + CUDA_CALLABLE quaternion(T c, T i, T j, T k){ + w=c; x=i; y=j; z=k; + } + + // create a pure quaternion from a vector + CUDA_CALLABLE quaternion(vec3 v){ + w = 0; x = v[0]; y = v[1]; z = v[2]; + } + + CUDA_CALLABLE quaternion conj(){ + quaternion c; + c.w = w; + c.x = -x; + c.y = -y; + c.z = -z; + return c; + } + +}; + +} //end rts namespace + + +#endif diff --git a/tira/math/random.h b/tira/math/random.h new file mode 100644 index 0000000..6643936 --- /dev/null +++ b/tira/math/random.h @@ -0,0 +1,112 @@ +#ifndef STIM_RANDOM +#define STIM_RANDOM + +#include +#include +#include +#include +#include + +namespace stim{ + +template +class Random{ +protected: + void init() { + srand(time(NULL)); + } + + void init(unsigned int seed){ + srand(seed); + } + +public: + /// Default Constructor + Random(){ + init(); + } + + /// Constructor from a seed. + /// A positive seed sets, 0 or negative yeilds the + Random(unsigned int seed){ + init(seed); + } + + ///Returns a random number uniformly sampled between 0 and 1 + static + T uniformRandom() + { + return ( (T)(rand()))/( (T)(RAND_MAX)); ///generates a random number between 0 and 1 using the uniform distribution. + } + + ///Returns a random number from a normal distribution between 0 to 1. + static + T normalRandom() + { + T u1 = uniformRandom(); + T u2 = uniformRandom(); + return cos(2.0*atan(1.0)*u2)*sqrt(-1.0*log(u1)); ///generate a random number using the normal distribution between 0 and 1. + } + ///Return a random vec3 each value between 0 and 1 from a uniform distribution. + static + stim::vec3 uniformRandVector() + { + stim::vec3 r(uniformRandom(), uniformRandom(), 1.0); ///generate a random vector using the uniform distribution between 0 and 1. + return r; + } + ///Return a random vec3, each value between 0 and 1 from a normal distribution. + static + stim::vec3 normalRandVector() + { + stim::vec3 r(normalRandom(), normalRandom(), 1.0); ///generate a random vector using the normal distribution between 0 and 1. + return r; + } + + ///place num_samples of samples on the surface of a sphere of radius r. + ///returns an std::vector of vec3's in cartisian coordinates. + static std::vector > + sample_sphere(unsigned int num_samples, T radius = 1, T solidAngle = stim::TAU) + { + std::cout << "did this" << std::endl; + T PHI[2], Z[2], range; ///Range of angles in cylinderical coordinates + PHI[0] = solidAngle/2; ///project the solid angle into spherical coords + PHI[1] = asin(0); /// + Z[0] = cos(PHI[0]); ///project the z into spherical coordinates + Z[1] = cos(PHI[1]); /// + range = Z[0] - Z[1]; ///the range of all possible z values. + + T z, theta, phi; /// temporary individual + + std::vector > samples; + + //srand(100); ///set random seed + + for(int i = 0; i < num_samples; i++) ///for each sample + { + z = uniformRandom()*range + Z[1]; ///find a random z based on the solid angle + theta = uniformRandom() * stim::TAU; ///find theta + phi = acos(z); ///project into spherical coord phi + stim::vec3 sph(radius, theta, phi); ///assume spherical + stim::vec3 cart = sph.sph2cart(); ///conver to cartesisn + samples.push_back(cart); ///push into list + } + +///THIS IS DEBUGGING CODE, UNCOMMENT TO CHECK WHETHER THE SURFACE IS WELL SAMPLED! +/* + std::stringstream name; + for(int i = 0; i < num_samples; i++) + name << samples[i].str() << std::endl; + + + std::ofstream outFile; + outFile.open("Sampled Surface.txt"); + outFile << name.str().c_str(); +*/ + + return samples; ///return full list. + } +}; + +} + +#endif diff --git a/tira/math/realfield.cuh b/tira/math/realfield.cuh new file mode 100644 index 0000000..f29330a --- /dev/null +++ b/tira/math/realfield.cuh @@ -0,0 +1,262 @@ +#ifndef RTS_REALFIELD_H +#define RTS_REALFIELD_H + +#include "../visualization/colormap.h" +#include "../envi/envi.h" +#include "../math/rect.h" +#include "../cuda/devices.h" +#include "cublas_v2.h" +#include + + +namespace stim{ + +//multiply R = X * Y +template +__global__ void gpu_realfield_multiply(T* R, T* X, T* Y, unsigned int r0, unsigned int r1){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //calculate and store the result + R[i] = X[i] * Y[i]; + +} + +template +class realfield{ + + P* X[N]; //an array of N gpu pointers for each field component + int R[2]; //resolution of the slice + rect

shape; + + void process_filename(std::string name, std::string &prefix, std::string &postfix, + std::string &ext, unsigned int &digits) + { + std::stringstream ss(name); + std::string item; + std::vector elems; + while(std::getline(ss, item, '.')) //split the string at the '.' character (filename and extension) + { + elems.push_back(item); + } + + prefix = elems[0]; //prefix contains the filename (with wildcard '?' characters) + ext = elems[1]; //file extension (ex. .bmp, .png) + ext = std::string(".") + ext; //add a period back into the extension + + size_t i0 = prefix.find_first_of("?"); //find the positions of the first and last wildcard ('?'') + size_t i1 = prefix.find_last_of("?"); + + postfix = prefix.substr(i1+1); + prefix = prefix.substr(0, i0); + + digits = i1 - i0 + 1; //compute the number of wildcards + } + + void init() + { + for(unsigned int n=0; n(vec

(-1, -1, 0), vec

(-1, 1, 0), vec

(1, 1, 0)); //default geometry + clear(); //zero the field + //std::cout<<"realfield CONSTRUCTOR"<(X[n], filename, R[0], R[1], vmin, vmax, cmap); + } + + void toImages(std::string filename, bool global_max = true, stim::colormapType cmap = stim::cmBrewer) + { + std::string prefix, postfix, extension; + unsigned int digits; + process_filename(filename, prefix, postfix, extension, digits); //process the filename for wild cards + + cublasStatus_t stat; + cublasHandle_t handle; + + //create a CUBLAS handle + stat = cublasCreate(&handle); + if(stat != CUBLAS_STATUS_SUCCESS) + { + std::cout<<"CUBLAS Error: initialization failed"< maxAll) //if maxVal is larger, update the maxAll variable + maxAll = maxVal[n]; + + } + + cublasDestroy(handle); //destroy the CUBLAS handle + + P outputMax = abs(maxAll); //maximum value used for each output image + for(int n=0; n operator* (const realfield & rhs){ + + int maxThreads = stim::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //create a scalar field to store the result + realfield result(R[0], R[1]); + + for(int n=0; n>> (result.X[n], X[n], rhs.X[n], R[0], R[1]); + + return result; + } + + ///copy constructor + realfield(const realfield &rhs) + { + //first make a shallow copy + R[0] = rhs.R[0]; + R[1] = rhs.R[1]; + + for(unsigned int n=0; n +#include +#include +#include +#include +#include +#include +#include + +namespace stim{ + +//template for a rectangle class in ND space +template +class rect : plane +{ + /* + ^ O + | + | + Y P + | + | + O---------X---------> + */ + +protected: + + stim::vec3 X; + stim::vec3 Y; + +public: + + using stim::plane::n; + using stim::plane::P; + using stim::plane::N; + using stim::plane::U; + using stim::plane::rotate; + + ///base constructor. + CUDA_CALLABLE rect() + : plane() + { + init(); + } + + ///create a rectangle given a size and position in Z space. + ///@param size: size of the rectangle in ND space. + ///@param z_pos z coordinate of the rectangle. + CUDA_CALLABLE rect(T size, T z_pos = (T)0) + : plane(z_pos) + { + init(); //use the default setup + scale(size); //scale the rectangle + } + + + ///create a rectangle from a center point, normal + ///@param c: x,y,z location of the center. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE rect(vec3 c, vec3 n = vec3(0, 0, 1)) + : plane() + { + init(); //start with the default setting + normal(n); //orient + } + + ///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE rect(vec3 c, T s, vec3 n = vec3(0, 0, 1)) + : plane() + { + init(); //start with the default setting + scale(s); + center(c); + rotate(n, X, Y); + } + + ///creates a rectangle from a centerpoint and an X and Y direction vectors. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(vec3 center, vec3 directionX, vec3 directionY ) + : plane((directionX.cross(directionY)).norm(),center) + { + X = directionX; + Y = directionY; + } + + ///creates a rectangle from a size, centerpoint, X, and Y direction vectors. + ///@param size of the rectangle in ND space. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(T size, vec3 center, vec3 directionX, vec3 directionY ) + : plane((directionX.cross(directionY)).norm(),center) + { + X = directionX; + Y = directionY; + scale(size); + } + + ///creates a rectangle from a size, centerpoint, X, and Y direction vectors. + ///@param size of the rectangle in ND space, size[0] = size in X, size[1] = size in Y. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(vec3 size, vec3 center, vec3 directionX, vec3 directionY) + : plane((directionX.cross(directionY)).norm(), center) + { + X = directionX; + Y = directionY; + scale(size[0], size[1]); + } + + CUDA_CALLABLE void scale(T factor){ + X *= factor; + Y *= factor; + } + + ///scales a rectangle in ND space. + ///@param factor1: size of the scale in the X-direction. + ///@param factor2: size of the scale in the Y-direction. + CUDA_CALLABLE void scale(T factor1, T factor2) + { + X *= factor1; + Y *= factor2; + } + + ///@param n; vector with the normal. + ///Orients the rectangle along the normal n. + CUDA_CALLABLE void normal(vec3 n) + { + //orient the rectangle along the specified normal + rotate(n, X, Y); + } + + ///general init method that sets a general rectangle. + CUDA_CALLABLE void init() + { + X = vec3(1, 0, 0); + Y = vec3(0, 1, 0); + } + + //boolean comparison + bool operator==(const rect & rhs) + { + if(P == rhs.P && X == rhs.X && Y == rhs.Y) + return true; + else + return false; + } + + + //get the world space value given the planar coordinates a, b in [0, 1] + CUDA_CALLABLE stim::vec3 p(T a, T b) + { + stim::vec3 result; + //given the two parameters a, b = [0 1], returns the position in world space + vec3 A = this->P - X * (T)0.5 - Y * (T)0.5; + result = A + X * a + Y * b; + + return result; + } + + //parenthesis operator returns the world space given rectangular coordinates a and b in [0 1] + CUDA_CALLABLE stim::vec3 operator()(T a, T b) + { + return p(a, b); + } + + std::string str() + { + std::stringstream ss; + vec3 A = P - X * (T)0.5 - Y * (T)0.5; + ss<"<<"C="<"<<"D="< operator*(T rhs) + { + //scales the plane by a scalar value + + //create the new rectangle + rect result = *this; + result.scale(rhs); + + return result; + + } + + ///computes the distance between the specified point and this rectangle. + ///@param p: x, y, z coordinates of the point to calculate distance to. + CUDA_CALLABLE T dist(vec3 p) + { + //compute the distance between a point and this rect + + vec3 A = P - X * (T)0.5 - Y * (T)0.5; + + //first break the rect up into two triangles + triangle T0(A, A+X, A+Y); + triangle T1(A+X+Y, A+X, A+Y); + + + T d0 = T0.dist(p); + T d1 = T1.dist(p); + + if(d0 < d1) + return d0; + else + return d1; + } + + CUDA_CALLABLE T center(vec3 p) + { + this->P = p; + } + + ///Returns the maximum distance of the rectangle from a point p to the sides of the rectangle. + ///@param p: x, y, z point. + CUDA_CALLABLE T dist_max(vec3 p) + { + vec3 A = P - X * (T)0.5 - Y * (T)0.5; + T da = (A - p).len(); + T db = (A+X - p).len(); + T dc = (A+Y - p).len(); + T dd = (A+X+Y - p).len(); + + return std::max( da, std::max(db, std::max(dc, dd) ) ); + } +}; + +} //end namespace rts + +template +std::ostream& operator<<(std::ostream& os, stim::rect R) +{ + os< +#include +#include +#include +#include +#include +#include + +namespace stim{ + +//template for a rectangle class in ND space +template +struct rect +{ + /* + ^ O + | + | + Y C + | + | + O---------X---------> + */ + +private: + + stim::vec C; + stim::vec X; + stim::vec Y; + + CUDA_CALLABLE void scale(T factor){ + X *= factor; + Y *= factor; + } + + + +public: + + ///base constructor. + CUDA_CALLABLE rect(){ + init(); + } + + ///create a rectangle given a size and position in Z space. + ///@param size: size of the rectangle in ND space. + ///@param z_pos z coordinate of the rectangle. + CUDA_CALLABLE rect(T size, T z_pos = (T)0){ + init(); //use the default setup + scale(size); //scale the rectangle + C[2] = z_pos; + } + + + ///create a rectangle from a center point, normal + ///@param c: x,y,z location of the center. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE rect(vec c, vec n = vec(0, 0, 1)){ + init(); //start with the default setting + C = c; + normal(n); //orient + } + + ///create a rectangle from a center point, normal, and size + ///@param c: x,y,z location of the center. + ///@param s: size of the rectangle. + ///@param n: x,y,z direction of the normal. + CUDA_CALLABLE rect(vec c, T s, vec n = vec(0, 0, 1)){ + init(); //start with the default setting + C = c; + scale(s); + normal(n); //orient + } + + ///creates a rectangle from a centerpoint and an X and Y direction vectors. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(vec center, vec directionX, vec directionY ) + { + C = center; + X = directionX; + Y = directionY; + } + + ///creates a rectangle from a size, centerpoint, X, and Y direction vectors. + ///@param size of the rectangle in ND space. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(T size, vec center, vec directionX, vec directionY ) + { + C = center; + X = directionX; + Y = directionY; + scale(size); + } + + ///creates a rectangle from a size, centerpoint, X, and Y direction vectors. + ///@param size of the rectangle in ND space, size[0] = size in X, size[1] = size in Y. + ///@param center: x,y,z location of the center. + ///@param directionX: u,v,w direction of the X vector. + ///@param directionY: u,v,w direction of the Y vector. + CUDA_CALLABLE rect(vec size, vec center, vec directionX, vec directionY ) + { + C = center; + X = directionX; + Y = directionY; + scale(size[0], size[1]); + } + + ///scales a rectangle in ND space. + ///@param factor1: size of the scale in the X-direction. + ///@param factor2: size of the scale in the Y-direction. + CUDA_CALLABLE void scale(T factor1, T factor2){ + X *= factor1; + Y *= factor2; + } + + ///@param n; vector with the normal. + ///Orients the rectangle along the normal n. + CUDA_CALLABLE void normal(vec n){ //orient the rectangle along the specified normal + + n = n.norm(); //normalize, just in case + vec n_current = X.cross(Y).norm(); //compute the current normal + quaternion q; //create a quaternion + q.CreateRotation(n_current, n); //initialize a rotation from n_current to n + + //apply the quaternion to the vectors and position + X = q.toMatrix3() * X; + Y = q.toMatrix3() * Y; + } + + ///general init method that sets a general rectangle. + CUDA_CALLABLE void init(){ + C = vec(0, 0, 0); + X = vec(1, 0, 0); + Y = vec(0, 1, 0); + } + + //boolean comparison + bool operator==(const rect & rhs) + { + if(C == rhs.C && X == rhs.X && Y == rhs.Y) + return true; + else + return false; + } + + /******************************************* + Return the normal for the rect + *******************************************/ + CUDA_CALLABLE stim::vec n() + { + return (X.cross(Y)).norm(); + } + + //get the world space value given the planar coordinates a, b in [0, 1] + CUDA_CALLABLE stim::vec p(T a, T b) + { + stim::vec result; + //given the two parameters a, b = [0 1], returns the position in world space + vec A = C - X * (T)0.5 - Y * (T)0.5; + result = A + X * a + Y * b; + + return result; + } + + //parenthesis operator returns the world space given rectangular coordinates a and b in [0 1] + CUDA_CALLABLE stim::vec operator()(T a, T b) + { + return p(a, b); + } + + std::string str() + { + std::stringstream ss; + vec A = C - X * (T)0.5 - Y * (T)0.5; + ss<"<<"C="<"<<"D="< operator*(T rhs) + { + //scales the plane by a scalar value + + //create the new rectangle + rect result = *this; + result.scale(rhs); + + return result; + + } + + ///computes the distance between the specified point and this rectangle. + ///@param p: x, y, z coordinates of the point to calculate distance to. + CUDA_CALLABLE T dist(vec p) + { + //compute the distance between a point and this rect + + vec A = C - X * (T)0.5 - Y * (T)0.5; + + //first break the rect up into two triangles + triangle T0(A, A+X, A+Y); + triangle T1(A+X+Y, A+X, A+Y); + + + T d0 = T0.dist(p); + T d1 = T1.dist(p); + + if(d0 < d1) + return d0; + else + return d1; + } + + CUDA_CALLABLE T center(vec p) + { + C = p; + } + + ///Returns the maximum distance of the rectangle from a point p to the sides of the rectangle. + ///@param p: x, y, z point. + CUDA_CALLABLE T dist_max(vec p) + { + vec A = C - X * (T)0.5 - Y * (T)0.5; + T da = (A - p).len(); + T db = (A+X - p).len(); + T dc = (A+Y - p).len(); + T dd = (A+X+Y - p).len(); + + return std::max( da, std::max(db, std::max(dc, dd) ) ); + } +}; + +} //end namespace rts + +template +std::ostream& operator<<(std::ostream& os, stim::rect R) +{ + os< +#include +#include +#include +#include + +#define WIRE_SCALE 1.001 +namespace stim { + + template + class spharmonics { + + public: + std::vector C; //list of SH coefficients + + protected: + unsigned int mcN; //number of Monte-Carlo samples + unsigned int coeff_1d(unsigned int l, int m) { //convert (l,m) to i (1D coefficient index) + return pow(l + 1, 2) - (l - m) - 1; + } + void coeff_2d(size_t c, unsigned int& l, int& m) { //convert a 1D coefficient index into (l, m) + l = (unsigned int)ceil(sqrt((double)c + 1)) - 1; //the major index is equal to sqrt(c) - 1 + m = (int)(c - (size_t)(l * l)) - (int)l; //the minor index is calculated by finding the difference + } + + public: + spharmonics() { + mcN = 0; + } + spharmonics(size_t c) : spharmonics() { + resize(c); + } + + void push(T c) { + C.push_back(c); + } + + void resize(unsigned int n) { + C.resize(n); + } + + void setc(unsigned int l, int m, T value) { + u44nsigned int c = coeff_1d(l, m); + C[c] = value; + } + + T getc(unsigned int l, int m) { + unsigned int c = coeff_1d(l, m); + return C[c]; + } + + void setc(unsigned int c, T value) { + C[c] = value; + } + + unsigned int getSize() const { + return C.size(); + } + + std::vector getC() const { + return C; + } + //calculate the value of the SH basis function (l, m) at (theta, phi) + //here, theta = [0, PI], phi = [0, 2*PI] + T SH(unsigned int l, int m, T theta, T phi) { + //std::complex result = boost::math::spherical_harmonic(l, m, phi, theta); + //return result.imag() + result.real(); + + //this calculation is based on calculating the real spherical harmonics: + // https://en.wikipedia.org/wiki/Spherical_harmonics#Addition_theorem + if (m < 0) { + return sqrt(2.0) * pow(-1, m) * boost::math::spherical_harmonic(l, abs(m), phi, theta).imag(); + } + else if (m == 0) { + return boost::math::spherical_harmonic(l, m, phi, theta).real(); + } + else { + return sqrt(2.0) * pow(-1, m) * boost::math::spherical_harmonic(l, m, phi, theta).real(); + } + } + + /// Calculate the spherical harmonic result given a 1D coefficient index + T SH(size_t c, T theta, T phi) { + unsigned int l; + int m; + coeff_2d(c, l, m); + return SH(l, m, theta, phi); + } + + + + /// Initialize Monte-Carlo sampling of a function using N spherical harmonics coefficients + + /// @param N is the number of spherical harmonics coefficients used to represent the user function + void mcBegin(unsigned int coefficients) { + C.resize(coefficients, 0); + mcN = 0; + } + + void mcBegin(unsigned int l, int m) { + unsigned int c = pow(l + 1, 2) - (l - m); + mcBegin(c); + } + + void mcSample(T theta, T phi, T val) { + + int l, m; + T sh; + + l = m = 0; + for (unsigned int i = 0; i < C.size(); i++) { + + sh = SH(l, m, theta, phi); + C[i] += sh * val; + + m++; //increment m + + //if we're in a new tier, increment l and set m = -l + if (m > l) { + l++; + m = -l; + } + } //end for all coefficients + + //increment the number of samples + mcN++; + + } //end mcSample() + + void mcEnd() { + + //divide all coefficients by the number of samples + for (unsigned int i = 0; i < C.size(); i++) + C[i] /= mcN; + } + + /// Generates a PDF describing the probability distribution of points on a spherical surface + /// @param sph_pts is a list of points in spherical coordinates (theta, phi) where theta = [0, 2pi] and phi = [0, pi] + /// @param l is the maximum degree of the spherical harmonic function + /// @param m is the maximum order + void pdf(std::vector > sph_pts, unsigned int l, int m) { + mcBegin(l, m); //begin spherical harmonic sampling + unsigned int nP = sph_pts.size(); + for (unsigned int p = 0; p < nP; p++) { + mcSample(sph_pts[p][1], sph_pts[p][2], 1.0); + } + mcEnd(); + } + + void pdf(std::vector > sph_pts, size_t c) { + unsigned int l; + int m; + coeff_2d(c, l, m); + pdf(sph_pts, l, m); + } + + /// Project a set of samples onto a spherical harmonic basis + void project(std::vector > sph_pts, unsigned int l, int m) { + mcBegin(l, m); //begin spherical harmonic sampling + unsigned int nP = sph_pts.size(); + for (unsigned int p = 0; p < nP; p++) { + mcSample(sph_pts[p][1], sph_pts[p][2], sph_pts[p][0]); + } + mcEnd(); + } + void project(std::vector > sph_pts, size_t c) { + unsigned int l; + int m; + coeff_2d(c, l, m); + project(sph_pts, l, m); + } + + /// Generates a PDF describing the density distribution of points on a sphere + /// @param sph_pts is a list of points in cartesian coordinates + /// @param l is the maximum degree of the spherical harmonic function + /// @param m is the maximum order + /// @param c is the centroid of the points in sph_pts. DEFAULT 0,0,0 + /// @param n is the number of points of the surface of the sphere used to create the PDF. DEFAULT 1000 + /// @param norm, a boolean that sets where the output vectors will be normalized between 0 and 1. + /// @param + void pdf(std::vector > sph_pts, unsigned int l, int m, stim::vec3 c = stim::vec3(0, 0, 0), unsigned int n = 1000, bool norm = true, std::vector w = std::vector(), int normfactor = 1) + { + std::vector weights; ///the weight at each point on the surface of the sphere. + // weights.resize(n); + unsigned int nP = sph_pts.size(); ///sph_pts is the vectors we want to fit a spehrical harmonic to. + std::vector > sphere = stim::Random::sample_sphere(n, 1.0, stim::TAU); ///randomsample a sphere of radius 1 and return the sampled vectors. + if (w.size() < nP) + w = std::vector(nP, 1.0); //1 weight per each m. + + for (int i = 0; i < n; i++) //for each weight + { + T val = 0; + for (int j = 0; j < nP; j++) //for each vector + { + stim::vec3 temp = sph_pts[j] - c; + if (temp.dot(sphere[i]) > 0) + val += pow(temp.dot(sphere[i]), normfactor)*w[j]; + } + weights.push_back(val); + } + + mcBegin(l, m); //begin spherical harmonic sampling + + if (norm) + { + T min = *std::min_element(weights.begin(), weights.end()); + T max = *std::max_element(weights.begin(), weights.end()); + for (unsigned int i = 0; i < n; i++) + { + stim::vec3 sph = sphere[i].cart2sph(); + mcSample(sph[1], sph[2], (weights[i] - min) / (max - min)); + } + + } + else { + for (unsigned int i = 0; i < n; i++) + { + stim::vec3 sph = sphere[i].cart2sph(); + mcSample(sph[1], sph[2], weights[i]); + } + } + mcEnd(); + } + + std::string str() { + + std::stringstream ss; + + int l, m; + l = m = 0; + for (unsigned int i = 0; i < C.size(); i++) { + + ss << C[i] << '\t'; + + m++; //increment m + + //if we're in a new tier, increment l and set m = -l + if (m > l) { + l++; + m = -l; + + ss << std::endl; + + } + } + + return ss.str(); + + + } + + /// Returns the value of the function at coordinate (theta, phi) + T p(T theta, T phi) { + T fx = 0; + + int l = 0; + int m = 0; + for (unsigned int i = 0; i < C.size(); i++) { + fx += C[i] * SH(l, m, theta, phi); + m++; + if (m > l) { + l++; + m = -l; + } + } + return fx; + } + + /// Returns the derivative of the spherical function with respect to theta + /// return value is in cartesian coordinates + vec3 dtheta(T theta, T phi, T d = 0.01) { + T r = p(theta, phi); //calculate the value of the spherical function at three points + T rt = p(theta + d, phi); + //double rp = p(theta, phi + d); + + vec3 s(r, theta, phi); //get the spherical coordinate position for all three points + vec3 st(rt, theta + d, phi); + //vec3 sp(rp, theta, phi + d); + + vec3 c = s.sph2cart(); + vec3 ct = st.sph2cart(); + //vec3 cp = sp.sph2cart(); + + vec3 dt = (ct - c)/d; //calculate the derivative + return dt; + } + + /// Returns the derivative of the spherical function with respect to phi + /// return value is in cartesian coordinates + vec3 dphi(T theta, T phi, T d = 0.01) { + T r = p(theta, phi); //calculate the value of the spherical function at three points + //double rt = p(theta + d, phi); + T rp = p(theta, phi + d); + + vec3 s(r, theta, phi); //get the spherical coordinate position for all three points + //vec3 st(rt, theta + d, phi); + vec3 sp(rp, theta, phi + d); + + vec3 c = s.sph2cart(); + //vec3 ct = st.sph2cart(); + vec3 cp = sp.sph2cart(); + + vec3 dp = (cp - c) / d; //calculate the derivative + return dp; + } + + /// Returns the value of the function at the coordinate (theta, phi) + /// @param theta = [0, 2pi] + /// @param phi = [0, pi] + T operator()(T theta, T phi) { + return p(theta, phi); + } + + //overload arithmetic operations + + spharmonics operator*(T rhs) const { + + spharmonics result(C.size()); //create a new spherical harmonics object + + for (size_t c = 0; c < C.size(); c++) //for each coefficient + + result.C[c] = C[c] * rhs; //calculate the factor and store the result in the new spharmonics object + + return result; + + } + + + + spharmonics operator+(spharmonics rhs) { + + size_t low = std::min(C.size(), rhs.C.size()); //store the number of coefficients in the lowest object + size_t high = std::max(C.size(), rhs.C.size()); //store the number of coefficients in the result + bool rhs_lowest = false; //true if rhs has the lowest number of coefficients + if (rhs.C.size() < C.size()) rhs_lowest = true; //if rhs has a lower number of coefficients, set the flag + + + + spharmonics result(high); //create a new object + + size_t c; + for (c = 0; c < low; c++) //perform the first batch of additions + result.C[c] = C[c] + rhs.C[c]; //perform the addition + + for (c = low; c < high; c++) { + if (rhs_lowest) + result.C[c] = C[c]; + else + result.C[c] = rhs.C[c]; + } + return result; + } + + + + spharmonics operator-(spharmonics rhs) { + return (*this) + (rhs * (T)(-1)); + } + /// Fill an NxN grid with the spherical function for theta = [0 2pi] and phi = [0 pi] + void get_func(T* data, size_t X, size_t Y) { + T dt = stim::TAU / (T)X; //calculate the step size in each direction + T dp = stim::PI / (T)(Y - 1); + for (size_t ti = 0; ti < X; ti++) { + for (size_t pi = 0; pi < Y; pi++) { + data[pi * X + ti] = (*this)((T)ti * dt, (T)pi * dp); + } + } + } + + /// Project a spherical function onto the basis using C coefficients + /// @param data is a pointer to the function values in (theta, phi) coordinates + /// @param N is the number of samples along each axis, where theta = [0 2pi), phi = [0 pi] + void project(T* data, size_t x, size_t y, size_t nc) { + stim::cpu2image(data, "test.ppm", x, y, stim::cmBrewer); + C.resize(nc, 0); //resize the coefficient array to store the necessary coefficients + T dtheta = stim::TAU / (T)(x - 1); //calculate the grid spacing along theta + T dphi = stim::PI / (T)y; //calculate the grid spacing along phi + T theta, phi; + for (size_t c = 0; c < nc; c++) { //for each coefficient + for (size_t theta_i = 0; theta_i < x; theta_i++) { //for each coordinate in the provided array + theta = theta_i * dtheta; //calculate theta + for (size_t phi_i = 0; phi_i < y; phi_i++) { + phi = phi_i * dphi; //calculate phi + C[c] += data[phi_i * x + theta_i] * SH(c, theta, phi) * dtheta * dphi * sin(phi); + } + } + } + } + + ///imperpolates a spherical harmonic linearly from itself to the spherical harmonic passed as an input. + ///@param target, spharmonic class to which we are interpolating. + ///@param dist float value representing the distance we are interpolating [0,1] + stim::spharmonics + interp(stim::spharmonics target, float dist) + { + stim::spharmonics ret(std::max(target.C.size(), C.size())); + for(int i = 0; i < target.C.size(); i++) + { + if(i < C.size()) + { + T slope = target.C[i] - C[i]; + ret.C[i] = C[i] + dist * slope; + } + else + { + T slope = target.C[i]; + ret.C[i] = dist * slope; + } + } + return ret; + } + + /// Generate spherical harmonic coefficients based on a set of N samples + /*void fit(std::vector > sph_pts, unsigned int L, bool norm = true) + { + //std::vector coeffs; + + //generate a matrix for fitting + int B = L*(L+2)+1; //calculate the matrix size + stim::matrix mat(B, B); //allocate space for the matrix + + + + std::vector sums; + //int B = l*(l+2)+1; + coeffs.resize(B); + sums.resize(B); + //stim::matrix mat(B, B); + for(int i = 0; i < sph_pts.size(); i++) + { + mcBegin(l,m); + mcSample(sph_pts[i][1], sph_pts[i][2], 1.0); + for(int j = 0; j < B; j++) + { + sums[j] += C[j]; + // sums[j] += C[j]*sums[j]; + } + mcEnd(); + } + for(int i = 0; i < B; i++) + { + for(int j = 0; j < B; j++) + { + mat(i,j) = sums[i]*sums[j]; + } + } + + if(mat.det() == 0) + { + std::cerr << " matrix not solvable " << std::endl; + } + else + { + //for(int i = 0; i < + } + }*/ + + + + + + }; //end class sph_harmonics + + + + +} + + +#endif diff --git a/tira/math/tensor2.h b/tira/math/tensor2.h new file mode 100644 index 0000000..fc0f639 --- /dev/null +++ b/tira/math/tensor2.h @@ -0,0 +1,48 @@ +#ifndef STIM_TENSOR2_H +#define STIM_TENSOR2_H + +#include "matrix_sym.h" + +namespace stim { + +/*This class represents a symmetric rank-2 2D tensor, useful for structure tensors +*/ +template +class tensor2 : public matrix_sym { + +protected: + +public: + + //calculate the eigenvectors and eigenvalues of the tensor + CUDA_CALLABLE void eig(stim::matrix& v, stim::matrix& lambda) { + + lambda = 0; //initialize the eigenvalue matrix to zero + + T t = M[0] + M[2]; //calculate the trace of the tensor + T d = M[0] * M[2] - M[1] * M[1]; //calculate the determinant of the tensor + + lambda(0, 0) = t / 2 + sqrt(t*t / 4 - d); + lambda(1, 1) = t / 2 - sqrt(t*t / 4 - d); + + if (M[1] == 0) { + v = stim::matrix::identity(); + } + else { + v(0, 0) = lambda(0, 0) - d; + v(0, 1) = lambda(1, 1) - d; + v(1, 0) = v(1, 1) = M[1]; + } + } + + CUDA_CALLABLE tensor2 operator=(stim::matrix_sym rhs){ + stim::matrix_sym::operator=(rhs); + return *this; + } +}; + + +} //end namespace stim + + +#endif \ No newline at end of file diff --git a/tira/math/tensor3.h b/tira/math/tensor3.h new file mode 100644 index 0000000..acfacfe --- /dev/null +++ b/tira/math/tensor3.h @@ -0,0 +1,200 @@ +#ifndef STIM_TENSOR3_H +#define STIM_TENSOR3_H + +#include "matrix_sym.h" +#include + +namespace stim { + + /*This class represents a symmetric rank-2 2D tensor, useful for structure tensors + */ + + //Matrix ID cheat sheet + // | 0 1 2 | + // | 1 3 4 | + // | 2 4 5 | + template + class tensor3 : public matrix_sym { + + protected: + + public: + + //calculates the determinant of the tensor + CUDA_CALLABLE T det() { + return M[0] * M[3] * M[5] + 2 * (M[1] * M[4] * M[2]) - M[2] * M[3] * M[2] - M[1] * M[1] * M[5] - M[0] * M[4] * M[4]; + } + + //calculate the eigenvalues for the tensor + //adapted from https://en.wikipedia.org/wiki/Eigenvalue_algorithm + + CUDA_CALLABLE stim::vec3 lambda() { + stim::vec3 lam; + T p1 = M[1] * M[1] + M[2] * M[2] + M[4] * M[4]; //calculate the sum of the squared off-diagonal values + if (p1 == 0) { //if this value is zero, the matrix is diagonal + lam[0] = M[0]; //the eigenvalues are the diagonal values + lam[1] = M[3]; + lam[2] = M[5]; + return lam; //return the eigenvalue vector + } + + T tr = matrix_sym::trace(); //calculate the trace of the matrix + T q = tr / 3; + T p2 = (M[0] - q) * (M[0] - q) + (M[3] - q) * (M[3] - q) + (M[5] - q) * (M[5] - q) + 2 * p1; + T p = sqrt(p2 / 6); + tensor3 Q; //allocate space for Q (q along the diagonals) + Q = (T)0; //initialize Q to zeros + Q(0, 0) = Q(1, 1) = Q(2, 2) = q; //set the diagonal values to q + tensor3 B = *this; // B1 = A + B.M[0] = (B.M[0] - q); + B.M[3] = (B.M[3] - q); + B.M[5] = (B.M[5] - q); + matrix_sym::operator_product(B, 1/p); // B = (1/p) * (A - q*I) + //B.M[0] = B.M[0] * 1/p; + //B.M[1] = B.M[1] * 1/p; + //B.M[2] = B.M[2] * 1/p; + //B.M[3] = B.M[3] * 1/p; + //B.M[4] = B.M[4] * 1/p; + //B.M[5] = B.M[5] * 1/p; + T r = B.det() / 2; //calculate det(B) / 2 + + // In exact arithmetic for a symmetric matrix - 1 <= r <= 1 + // but computation error can leave it slightly outside this range. + T phi; + if (r <= -1) phi = stim::PI / 3; + else if (r >= 1) phi = 0; + else phi = acos(r) / 3; + + // the eigenvalues satisfy eig3 >= eig2 >= eig1 + lam[2] = q + 2 * p * cos(phi); + lam[0] = q + 2 * p * cos(phi + (2 * stim::PI / 3)); + lam[1] = 3 * q - (lam[2] + lam[0]); + + return lam; + } + + CUDA_CALLABLE stim::matrix eig(stim::vec3& lambda = stim::vec3()) { + stim::matrix V; + + stim::matrix M1 = matrix_sym::mat(); + stim::matrix M2 = matrix_sym::mat(); + stim::matrix M3 = matrix_sym::mat(); // fill a tensor with symmetric values + + M1 = M1 - lambda[0]; // M1 = A - lambda[0] * I + + M2 = M2 - lambda[1]; // M2 = A - lambda[1] * I + + M3 = M3 - lambda[2]; // M3 = A - lambda[2] * I + + T Mod = 0; // module of one column + + T tmp1[9] = {0}; + for(int i = 0; i < 9; i++) { + for(int j = 0; j < 3; j++){ + tmp1[i] += M2(i%3, j) * M3(j, i/3); + } + } + if(tmp1[0] * tmp1[1] * tmp1[2] != 0) { // test whether it is zero column + Mod = sqrt(pow(tmp1[0],2) + pow(tmp1[1],2) + pow(tmp1[2],2)); + V(0, 0) = tmp1[0]/Mod; + V(1, 0) = tmp1[1]/Mod; + V(2, 0) = tmp1[2]/Mod; + } + else { + Mod = sqrt(pow(tmp1[3],2) + pow(tmp1[4],2) + pow(tmp1[5],2)); + V(0, 0) = tmp1[3]/Mod; + V(1, 0) = tmp1[4]/Mod; + V(2, 0) = tmp1[5]/Mod; + } + + T tmp2[9] = {0}; + for(int i = 0; i < 9; i++) { + for(int j = 0; j < 3; j++){ + tmp2[i] += M1(i%3, j) * M3(j, i/3); + } + } + if(tmp2[0] * tmp2[1] * tmp2[2] != 0) { + Mod = sqrt(pow(tmp2[0],2) + pow(tmp2[1],2) + pow(tmp2[2],2)); + V(0, 1) = tmp2[0]/Mod; + V(1, 1) = tmp2[1]/Mod; + V(2, 1) = tmp2[2]/Mod; + } + else { + Mod = sqrt(pow(tmp2[3],2) + pow(tmp2[4],2) + pow(tmp2[5],2)); + V(0, 1) = tmp2[3]/Mod; + V(1, 1) = tmp2[4]/Mod; + V(2, 1) = tmp2[5]/Mod; + } + + T tmp3[9] = {0}; + for(int i = 0; i < 9; i++) { + for(int j = 0; j < 3; j++){ + tmp3[i] += M1(i%3, j) * M2(j, i/3); + } + } + if(tmp3[0] * tmp3[1] * tmp3[2] != 0) { + Mod = sqrt(pow(tmp3[0],2) + pow(tmp3[1],2) + pow(tmp3[2],2)); + V(0, 2) = tmp3[0]/Mod; + V(1, 2) = tmp3[1]/Mod; + V(2, 2) = tmp3[2]/Mod; + } + else { + Mod = sqrt(pow(tmp3[3],2) + pow(tmp3[4],2) + pow(tmp3[5],2)); + V(0, 2) = tmp3[3]/Mod; + V(1, 2) = tmp3[4]/Mod; + V(2, 2) = tmp3[5]/Mod; + } + return V; //return the eigenvector matrix + } + // return one specific eigenvector + CUDA_CALLABLE stim::vec3 eig(int n, stim::vec3& lambda = stim::vec3()) { + stim::matrix V = eig(lambda); + stim::vec3 v; + for(int i = 0; i < 3; i++) + v[i] = V(i, n); + return v; + } + + + CUDA_CALLABLE T linear(stim::vec3& lambda = stim::vec3()) { + T cl = (lambda[2] - lambda[1]) / (lambda[0] + lambda[1] + lambda[2]); + return cl; + } + + CUDA_CALLABLE T Planar(stim::vec3& lambda = stim::vec3()) { + T cp = 2 * (lambda[1] - lambda[0]) / (lambda[0] + lambda[1] + lambda[2]); + return cp; + } + + CUDA_CALLABLE T spherical(stim::vec3& lambda = stim::vec3()) { + T cs = 3 * lambda[0] / (lambda[0] + lambda[1] + lambda[2]); + return cs; + } + + CUDA_CALLABLE T fa(stim::vec3& lambda = stim::vec3()) { + T fa = sqrt(1/2) * sqrt(pow(lambda[2] - lambda[1], 2) + pow(lambda[1] - lambda[0], 2) + pow(lambda[0] - lambda[2], 2)) / sqrt(pow(lambda[2], 2) + pow(lambda[1], 2) + pow(lambda[0], 2)); + } + //JACK 2: write functions to calculate anisotropy + //ex: fa(), linear(), planar(), spherical() + + + //calculate the eigenvectors and eigenvalues of the tensor + //CUDA_CALLABLE void eig(stim::matrix& v, stim::matrix& lambda){ + + //} + CUDA_CALLABLE tensor3 operator=(T rhs) { + stim::matrix_sym::operator=(rhs); + return *this; + } + + CUDA_CALLABLE tensor3 operator=(stim::matrix_sym rhs) { + stim::matrix_sym::operator=(rhs); + return *this; + } + }; + + +} //end namespace stim + + +#endif \ No newline at end of file diff --git a/tira/math/triangle.h b/tira/math/triangle.h new file mode 100644 index 0000000..2f2bc2c --- /dev/null +++ b/tira/math/triangle.h @@ -0,0 +1,188 @@ +#ifndef RTS_TRIANGLE_H +#define RTS_TRIANGLE_H + +//enable CUDA_CALLABLE macro +#include +#include +#include + +namespace stim{ + +template +struct triangle +{ + /* + A------>B + | / + | / + | / + | / + | / + | / + C + */ + private: + + vec A; + vec B; + vec C; + + CUDA_CALLABLE vec _p(T s, T t) + { + //This function returns the point specified by p = A + s(B-A) + t(C-A) + vec E0 = B-A; + vec E1 = C-A; + + return A + s*E0 + t*E1; + } + + + public: + + + + CUDA_CALLABLE triangle() + { + + } + + CUDA_CALLABLE triangle(vec a, vec b, vec c) + { + A = a; + B = b; + C = c; + } + + CUDA_CALLABLE stim::vec operator()(T s, T t) + { + return _p(s, t); + } + + CUDA_CALLABLE vec nearest(vec p) + { + //comptue the distance between a point and this triangle + // This code is adapted from: http://www.geometrictools.com/Documentation/DistancePoint3Triangle3.pdf + + vec E0 = B-A; + vec E1 = C-A; + vec D = A - p; + + T a = E0.dot(E0); + T b = E0.dot(E1); + T c = E1.dot(E1); + T d = E0.dot(D); + T e = E1.dot(D); + //T f = D.dot(D); + + T det = a*c - b*b; + T s = b*e - c*d; + T t = b*d - a*e; + + /*std::cout<<"E0: "<= 0 ? 0 : ( -e >= c ? 1 : -e/c ) ); + //done + } + } + else if(t < 0) + { + //region 5 + //std::cout<<"Region 5"<= 0 ? 0 : ( -d >= a ? 1 : -d/a ) ); + t = 0; + //done + } + else + { + //region 0 + //std::cout<<"Region 0"<= denom ? 1 : numer/denom ); + } + t = 1 - s; + //done + } + } + + //std::cout<<"s: "< p) + { + vec n = nearest(p); + + return (p - n).len(); + } +}; + +} + +#endif diff --git a/tira/math/vec3.h b/tira/math/vec3.h new file mode 100644 index 0000000..533075c --- /dev/null +++ b/tira/math/vec3.h @@ -0,0 +1,347 @@ +#ifndef TIRA_VEC3_H +#define TIRA_VEC3_H + + +#include +#include +#include + +#include + + +namespace tira{ + + +/// A class designed to act as a 3D vector with CUDA compatibility +template +class vec3{ + +protected: + T ptr[3]; + +public: + + CUDA_CALLABLE vec3(){} + + CUDA_CALLABLE vec3(T v){ + ptr[0] = ptr[1] = ptr[2] = v; + } + + CUDA_CALLABLE vec3(T x, T y, T z){ + ptr[0] = x; + ptr[1] = y; + ptr[2] = z; + } + + //copy constructor + CUDA_CALLABLE vec3( const vec3& other){ + ptr[0] = other.ptr[0]; + ptr[1] = other.ptr[1]; + ptr[2] = other.ptr[2]; + } + + //access an element using an index + CUDA_CALLABLE T& operator[](size_t idx){ + return ptr[idx]; + } + //read only accessor method + CUDA_CALLABLE T get(size_t idx) const { + return ptr[idx]; + } + + CUDA_CALLABLE T* data(){ + return ptr; + } + +/// Casting operator. Creates a new vector with a new type U. +/* template< typename U > + CUDA_CALLABLE operator vec3(){ + vec3 result((U)ptr[0], (U)ptr[1], (U)ptr[2]); + //result.ptr[0] = (U)ptr[0]; + //result.ptr[1] = (U)ptr[1]; + //result.ptr[2] = (U)ptr[2]; + + return result; + }*/ + + // computes the squared Euclidean length (useful for several operations where only >, =, or < matter) + CUDA_CALLABLE T len_sq() const{ + return ptr[0] * ptr[0] + ptr[1] * ptr[1] + ptr[2] * ptr[2]; + } + + /// computes the Euclidean length of the vector + CUDA_CALLABLE T len() const{ + return sqrt(len_sq()); + } + + /// Calculate the L2 norm of the vector, accounting for the possibility that it is complex + CUDA_CALLABLE T norm2() const{ + T conj_dot = std::real(ptr[0] * std::conj(ptr[0]) + ptr[1] * std::conj(ptr[1]) + ptr[2] * std::conj(ptr[2])); + return sqrt(conj_dot); + } + + /// Calculate the normalized direction vector + CUDA_CALLABLE vec3 direction() const { + vec3 result; + T length = norm2(); + result[0] = ptr[0] / length; + result[1] = ptr[1] / length; + result[2] = ptr[2] / length; + return result; + } + + /// Convert the vector from cartesian to spherical coordinates (x, y, z -> r, theta, phi where theta = [-PI, PI]) + CUDA_CALLABLE vec3 cart2sph() const{ + vec3 sph; + sph.ptr[0] = len(); + sph.ptr[1] = std::atan2(ptr[1], ptr[0]); + if(sph.ptr[0] == 0) + sph.ptr[2] = 0; + else + sph.ptr[2] = std::acos(ptr[2] / sph.ptr[0]); + return sph; + } + + CUDA_CALLABLE vec3 cart2cyl() const{ + vec3 cyl; + cyl.ptr[0] = sqrt(pow(ptr[0],2) + pow(ptr[1],2)); + cyl.ptr[1] = atan(ptr[1]/ptr[0]); + cyl.ptr[2] = ptr[2]; + + return cyl; + } + + /// Convert the vector from cartesian to spherical coordinates (r, theta, phi -> x, y, z where theta = [0, 2*pi]) + CUDA_CALLABLE vec3 sph2cart() const{ + vec3 cart; + cart.ptr[0] = ptr[0] * std::cos(ptr[1]) * std::sin(ptr[2]); + cart.ptr[1] = ptr[0] * std::sin(ptr[1]) * std::sin(ptr[2]); + cart.ptr[2] = ptr[0] * std::cos(ptr[2]); + + return cart; + } + + /// Convert the vector from cylindrical to cart coordinates (r, theta, z -> x, y, z where theta = [0, 2*pi]) + CUDA_CALLABLE vec3 cyl2cart() const{ + vec3 cart; + cart.ptr[0] = ptr[0] * std::cos(ptr[1]); + cart.ptr[1] = ptr[0] * std::sin(ptr[1]); + cart.ptr[2] = ptr[2]; + + return cart; + } + + /// Computes the normalized vector (where each coordinate is divided by the L2 norm) + CUDA_CALLABLE vec3 norm() const{ + vec3 result; + T l = len(); //compute the vector length + return (*this) / l; + } + + /// Computes the cross product of a 3-dimensional vector + CUDA_CALLABLE vec3 cross(const vec3 rhs) const{ + + vec3 result; + + result[0] = (ptr[1] * rhs.ptr[2] - ptr[2] * rhs.ptr[1]); + result[1] = (ptr[2] * rhs.ptr[0] - ptr[0] * rhs.ptr[2]); + result[2] = (ptr[0] * rhs.ptr[1] - ptr[1] * rhs.ptr[0]); + + return result; + } + + /// Compute the Euclidean inner (dot) product + CUDA_CALLABLE T dot(vec3 rhs) const{ + return ptr[0] * rhs.ptr[0] + ptr[1] * rhs.ptr[1] + ptr[2] * rhs.ptr[2]; + } + + /// Arithmetic addition operator + + /// @param rhs is the right-hand-side operator for the addition + CUDA_CALLABLE vec3 operator+(vec3 rhs) const{ + vec3 result; + result.ptr[0] = ptr[0] + rhs[0]; + result.ptr[1] = ptr[1] + rhs[1]; + result.ptr[2] = ptr[2] + rhs[2]; + return result; + } + + /// Arithmetic addition to a scalar + + /// @param rhs is the right-hand-side operator for the addition + CUDA_CALLABLE vec3 operator+(T rhs) const{ + vec3 result; + result.ptr[0] = ptr[0] + rhs; + result.ptr[1] = ptr[1] + rhs; + result.ptr[2] = ptr[2] + rhs; + return result; + } + + /// Arithmetic subtraction operator + + /// @param rhs is the right-hand-side operator for the subtraction + CUDA_CALLABLE vec3 operator-(vec3 rhs) const{ + vec3 result; + result.ptr[0] = ptr[0] - rhs[0]; + result.ptr[1] = ptr[1] - rhs[1]; + result.ptr[2] = ptr[2] - rhs[2]; + return result; + } + /// Arithmetic subtraction to a scalar + + /// @param rhs is the right-hand-side operator for the addition + CUDA_CALLABLE vec3 operator-(T rhs) const{ + vec3 result; + result.ptr[0] = ptr[0] - rhs; + result.ptr[1] = ptr[1] - rhs; + result.ptr[2] = ptr[2] - rhs; + return result; + } + + /// Arithmetic scalar multiplication operator + + /// @param rhs is the right-hand-side operator for the subtraction + CUDA_CALLABLE vec3 operator*(T rhs) const{ + vec3 result; + result.ptr[0] = ptr[0] * rhs; + result.ptr[1] = ptr[1] * rhs; + result.ptr[2] = ptr[2] * rhs; + return result; + } + + /// Arithmetic scalar division operator + + /// @param rhs is the right-hand-side operator for the subtraction + CUDA_CALLABLE vec3 operator/(T rhs) const{ + return (*this) * ((T)1.0/rhs); + } + + /// Multiplication by a scalar, followed by assignment + CUDA_CALLABLE vec3 operator*=(T rhs){ + ptr[0] = ptr[0] * rhs; + ptr[1] = ptr[1] * rhs; + ptr[2] = ptr[2] * rhs; + return *this; + } + + /// Addition and assignment + CUDA_CALLABLE vec3 operator+=(vec3 rhs){ + ptr[0] = ptr[0] + rhs; + ptr[1] = ptr[1] + rhs; + ptr[2] = ptr[2] + rhs; + return *this; + } + + /// Assign a scalar to all values + CUDA_CALLABLE vec3 & operator=(T rhs){ + ptr[0] = ptr[0] = rhs; + ptr[1] = ptr[1] = rhs; + ptr[2] = ptr[2] = rhs; + return *this; + } + + /// Casting and assignment + template + CUDA_CALLABLE vec3 & operator=(vec3 rhs){ + ptr[0] = (T)rhs.ptr[0]; + ptr[1] = (T)rhs.ptr[1]; + ptr[2] = (T)rhs.ptr[2]; + return *this; + } + + /// Unary minus (returns the negative of the vector) + CUDA_CALLABLE vec3 operator-() const{ + vec3 result; + result.ptr[0] = -ptr[0]; + result.ptr[1] = -ptr[1]; + result.ptr[2] = -ptr[2]; + return result; + } + + CUDA_CALLABLE bool operator==(vec3 rhs) const{ + if(rhs[0] == ptr[0] && rhs[1] == ptr[1] && rhs[2] == ptr[2]) + return true; + else + return false; + } + +//#ifndef __CUDACC__ + /// Outputs the vector as a string +std::string str() const{ + std::stringstream ss; + + const size_t N = 3; + + ss<<"["; + for(size_t i=0; i + class cvec3 : public vec3< std::complex > { + + public: + CUDA_CALLABLE cvec3() : vec3< std::complex >() {} + CUDA_CALLABLE cvec3(T x, T y, T z) : vec3< std::complex >(std::complex(x), std::complex(y), std::complex(z)) {} + CUDA_CALLABLE cvec3(std::complex x, std::complex y, std::complex z) : vec3< std::complex >(x, y, z) {} + + CUDA_CALLABLE cvec3& operator=(const vec3< std::complex > rhs) { + vec3< std::complex >::ptr[0] = rhs.get(0); + vec3< std::complex >::ptr[1] = rhs.get(1); + vec3< std::complex >::ptr[2] = rhs.get(2); + return *this; + } + CUDA_CALLABLE std::complex dot(const vec3 rhs) { + std::complex result = + vec3< std::complex >::ptr[0] * rhs.get(0) + + vec3< std::complex >::ptr[1] * rhs.get(1) + + vec3< std::complex >::ptr[2] * rhs.get(2); + + return result; + } + + /// @param rhs is the right-hand-side operator for the addition + CUDA_CALLABLE cvec3 operator+(cvec3 rhs) const { + return vec3< std::complex >::operator+(rhs); + } + /// @param rhs is the right-hand-side operator for adding a real vector to a complex vector + CUDA_CALLABLE cvec3 operator+(vec3 rhs) const { + cvec3 result; + result.ptr[0] = vec3< std::complex >::ptr[0] + rhs[0]; + result.ptr[1] = vec3< std::complex >::ptr[1] + rhs[1]; + result.ptr[2] = vec3< std::complex >::ptr[2] + rhs[2]; + return result; + } + }; //end class cvec3 +} //end namespace tira + + + + +/// Multiply a vector by a constant when the vector is on the right hand side +template +tira::vec3 operator*(T lhs, tira::vec3 rhs){ + return rhs * lhs; +} + +//stream operator +template +std::ostream& operator<<(std::ostream& os, tira::vec3 const& rhs){ + os< +#include +#include +#include +#include +#include + +#include +#include + +namespace tira +{ + +template +struct vec : public std::vector +{ + using std::vector::size; + using std::vector::at; + using std::vector::resize; + using std::vector::push_back; + + vec(){ + + } + + /// Create a vector with a set dimension d + vec(size_t d) + { + resize(d,0); + } + + +// //efficiency constructors, makes construction easier for 1D-4D vectors + vec(T x, T y) + { + resize(2, 0); + at(0) = x; + at(1) = y; + } + vec(T x, T y, T z) + { + resize(3, 0); + at(0) = x; + at(1) = y; + at(2) = z; + } + vec(T x, T y, T z, T w) + { + resize(4, 0); + at(0) = x; + at(1) = y; + at(2) = z; + at(3) = w; + } + + vec(std::string str){ + std::stringstream ss(str); + + T c; + while(ss >> c){ + push_back(c); + } + + } + + + + //copy constructor + vec( const vec& other){ + size_t N = other.size(); + resize(N); //resize the current vector to match the copy + for(size_t i=0; i& other){ +// resize(3); //resize the current vector to match the copy +// for(size_t i=0; i<3; i++){ //copy each element +// at(i) = other[i]; +// } +// } + + //I'm not sure what these were doing here. + //Keep them now, we'll worry about it later. + vec push(T x) + { + push_back(x); + return *this; + } + + vec push(T x, T y) + { + push_back(x); + push_back(y); + return *this; + } + vec push(T x, T y, T z) + { + push_back(x); + push_back(y); + push_back(z); + return *this; + } + vec push(T x, T y, T z, T w) + { + push_back(x); + push_back(y); + push_back(z); + push_back(w); + return *this; + } + + /// Casting operator. Creates a new vector with a new type U. + template< typename U > + operator vec(){ + size_t N = size(); + vec result; + for(int i=0; i cyl2cart() const + { + vec cyl; + cyl.push_back(at(0)*std::sin(at(1))); + cyl.push_back(at(0)*std::cos(at(1))); + cyl.push_back(at(2)); + return(cyl); + + } + /// Convert the vector from cartesian to spherical coordinates (x, y, z -> r, theta, phi where theta = [0, 2*pi]) + vec cart2sph() const + { + + + vec sph; + sph.push_back(std::sqrt(at(0)*at(0) + at(1)*at(1) + at(2)*at(2))); + sph.push_back(std::atan2(at(1), at(0))); + + if(sph[0] == 0) + sph.push_back(0); + else + sph.push_back(std::acos(at(2) / sph[0])); + + return sph; + } + + /// Convert the vector from cartesian to spherical coordinates (r, theta, phi -> x, y, z where theta = [0, 2*pi]) + vec sph2cart() const + { + vec cart; + cart.push_back(at(0) * std::cos(at(1)) * std::sin(at(2))); + cart.push_back(at(0) * std::sin(at(1)) * std::sin(at(2))); + cart.push_back(at(0) * std::cos(at(2))); + + return cart; + } + + /// Computes the normalized vector (where each coordinate is divided by the L2 norm) + vec norm() const + { + size_t N = size(); + + //compute and return the unit vector + vec result; + + //compute the vector length + T l = len(); + + //normalize + for(size_t i=0; i cross(const vec rhs) const + { + + vec result(3); + + //compute the cross product (only valid for 3D vectors) + result[0] = (at(1) * rhs[2] - at(2) * rhs[1]); + result[1] = (at(2) * rhs[0] - at(0) * rhs[2]); + result[2] = (at(0) * rhs[1] - at(1) * rhs[0]); + + return result; + } + + /// Compute the Euclidean inner (dot) product + T dot(vec rhs) const + { + T result = (T)0; + size_t N = size(); + for(int i=0; i operator+(vec rhs) const + { + size_t N = size(); + vec result(N); + + for(int i=0; i operator+(T rhs) const + { + size_t N = size(); + + vec result(N); + for(int i=0; i operator-(vec rhs) const + { + size_t N = size(); + + vec result(N); + + for(size_t i=0; i operator-(T rhs) const + { + size_t N = size(); + + vec result(N); + for(size_t i=0; i operator*(T rhs) const + { + size_t N = size(); + + vec result(N); + + for(size_t i=0; i operator/(T rhs) const + { + size_t N = size(); + + vec result(N); + + for(size_t i=0; i operator*=(T rhs){ + + size_t N = size(); + for(size_t i=0; i operator+=(vec rhs){ + size_t N = size(); + for(size_t i=0; i & operator=(T rhs){ + + size_t N = size(); + for(size_t i=0; i(){ + tira::vec3 r; + size_t N = std::min(size(), (size_t)3); + for(size_t i = 0; i < N; i++) + r[i] = at(i); + return r; + } + + + /// Casting and assignment + template + vec & operator=(vec rhs){ + size_t N = rhs.size(); + resize(N); + + for(size_t i=0; i + vec & operator=(vec3 rhs) + { + resize(3); + for(size_t i=0; i<3; i++) + at(i) = rhs[i]; + return *this; + } + + /// Unary minus (returns the negative of the vector) + vec operator-() const{ + + size_t N = size(); + + vec r(N); + + //negate the vector + for(size_t i=0; i +std::ostream& operator<<(std::ostream& os, tira::vec v) +{ + os< +tira::vec operator*(T lhs, tira::vec rhs) +{ + tira::vec r; + + return rhs * lhs; +} + +#endif diff --git a/tira/matlab/rtsApplyBrewer.m b/tira/matlab/rtsApplyBrewer.m new file mode 100644 index 0000000..ee140cc --- /dev/null +++ b/tira/matlab/rtsApplyBrewer.m @@ -0,0 +1,41 @@ +function result = rtsApplyBrewer(field, minVal, maxVal) + +if nargin < 3 + maxVal = max(field(:)); +end +if nargin < 2 + minVal = min(field(:)); +end + +fieldMaskMin = field >= minVal; +fieldMaskMax = field <= maxVal; + +fieldMask = min(fieldMaskMin, fieldMaskMax); + +ctrlPts = zeros(11, 3); + +ctrlPts(1, :) = [0.192157, 0.211765, 0.584314]; +ctrlPts(2, :) = [0.270588, 0.458824, 0.705882]; +ctrlPts(3, :) = [0.454902, 0.678431, 0.819608]; +ctrlPts(4, :) = [0.670588, 0.85098, 0.913725]; +ctrlPts(5, :) = [0.878431, 0.952941, 0.972549]; +ctrlPts(6, :) = [1, 1, 0.74902]; +ctrlPts(7, :) = [0.996078, 0.878431, 0.564706]; +ctrlPts(8, :) = [0.992157, 0.682353, 0.380392]; +ctrlPts(9, :) = [0.956863, 0.427451, 0.262745]; +ctrlPts(10, :) = [0.843137, 0.188235, 0.152941]; +ctrlPts(11, :) = [0.647059, 0, 0.14902]; + +%X = 0:1/10:1; +X = minVal:(maxVal - minVal)/10:maxVal; + +R = interp1(X, ctrlPts(:, 1), field); +G = interp1(X, ctrlPts(:, 2), field); +B = interp1(X, ctrlPts(:, 3), field); + +R = R.* fieldMask; +G = G.* fieldMask; +B = B.* fieldMask; + +result = cat(ndims(field)+1, R, G, B); + diff --git a/tira/matlab/rtsBaselineCorrect.m b/tira/matlab/rtsBaselineCorrect.m new file mode 100644 index 0000000..43ba602 --- /dev/null +++ b/tira/matlab/rtsBaselineCorrect.m @@ -0,0 +1,61 @@ +function R = rtsBaselineCorrect(A, b, dim) + +%This function baselines an n-dimensional array given a series of points b +%B = baseline-corrected data +%A = raw data +%b = baseline points in the format [b0 b1 ... bn] +%dim = dimension along which the baseline correction will occur + + +%allocate space for the sub-component indices +sz = size(A); +inds = repmat({1},1,ndims(A)); + +%allocate space for the result +R = zeros(size(A)); + +%for each baseline point +for bp = 1:length(b) - 1 + + %extract the data at the first baseline point + n = b(bp); + inds = getIndices(A, n, dim); + BP1 = A(inds{:}); + + %extract the data at the second baseline point + n = b(bp+1); + inds = getIndices(A, n, dim); + BP2 = A(inds{:}); + + %for each point in between the baseline points + for p = b(bp):b(bp+1) + + %compute the weighting of the linear function between BP1 and BP2 + a2 = (p - b(bp))/(b(bp+1) - b(bp)); + a1 = 1.0 - a2; + + n = p; + inds = getIndices(A, n, dim); + + baseFunc = a1 * BP1 + a2 * BP2; + R(inds{:}) = A(inds{:}) - baseFunc; + end +end + + +function I = getIndices(A, n, dim) + +%this function returns indices for all elements in S at +%point n along dimension dim + +sz = size(A); +I = repmat({1},1,ndims(A)); + +for d = 1:ndims(A) + if d ~= dim + I{d} = 1:sz(d); + else + I{d} = n; + end +end + diff --git a/tira/matlab/rtsClass2Color.m b/tira/matlab/rtsClass2Color.m new file mode 100644 index 0000000..7377873 --- /dev/null +++ b/tira/matlab/rtsClass2Color.m @@ -0,0 +1,15 @@ +function C = rtsClass2Color(classes, classList, colorList) + +C = zeros(length(classes), 3); + +for i = 1:length(classes) + + for c = 1:length(classList) + + if classes(i) == classList(c) + C(i, :) = colorList(c, :); + end + + end + +end \ No newline at end of file diff --git a/tira/matlab/rtsColorMapField.m b/tira/matlab/rtsColorMapField.m new file mode 100644 index 0000000..d947377 --- /dev/null +++ b/tira/matlab/rtsColorMapField.m @@ -0,0 +1,30 @@ +function Result = rtsColorMapField(F, range) +%creates a color map for the field F and returns an array with +%an additional size-3 color dimension + +R = zeros(size(F)); +G = zeros(size(F)); +B = zeros(size(F)); + +%if no range is specified, estimate it using min and max values +if nargin == 1 + range(1) = min(F(:)); + range(2) = max(F(:)); +end +range_size = range(2) - range(1); + +num_elements = size(F(:)); +for i =1:num_elements + if(F(i) > range(1) && F(i) < range(2)) + hue_degrees = (1 - (F(i) - range(1))/range_size)*240; + HSVval = [hue_degrees 1.0 1.0]; + RGBval = rtsHSVtoRGB(HSVval); + else + RGBval = [0 0 F(i)/range(1)]; + end + R(i) = RGBval(1); + G(i) = RGBval(2); + B(i) = RGBval(3); +end +Result = cat(ndims(F)+1, R, G, B); +imshow(Result); diff --git a/tira/matlab/rtsColorMapRainbow.m b/tira/matlab/rtsColorMapRainbow.m new file mode 100644 index 0000000..ff38316 --- /dev/null +++ b/tira/matlab/rtsColorMapRainbow.m @@ -0,0 +1,37 @@ +function result = rtsColorMapRainbow(field, minVal, maxVal) + +if nargin < 3 + maxVal = max(field(:)); +end +if nargin < 2 + minVal = min(field(:)); +end + +fieldMaskMin = field >= minVal; +fieldMaskMax = field <= maxVal; + +fieldMask = fieldMaskMin;%min(fieldMaskMin, fieldMaskMax); + +ctrlPts = zeros(3, 3); + +ctrlPts(1, :) = [0.0, 0.0, 0.5]; +ctrlPts(2, :) = [0.0, 0.0, 1.0]; +ctrlPts(3, :) = [0.0, 1.0, 1.0]; +ctrlPts(4, :) = [0.0, 1.0, 0.0]; +ctrlPts(5, :) = [1.0, 1.0, 0.0]; +ctrlPts(6, :) = [1.0, 0.0, 0.0]; +ctrlPts(7, :) = [0.5, 0.0, 0.0]; + +%X = 0:1/10:1; +X = minVal:(maxVal - minVal)/6:maxVal; + +R = interp1(X, ctrlPts(:, 1), field); +G = interp1(X, ctrlPts(:, 2), field); +B = interp1(X, ctrlPts(:, 3), field); + +%R = R.* fieldMask; +%G = G.* fieldMask; +%B = B.* fieldMask; + +result = cat(ndims(field)+1, R, G, B); + diff --git a/tira/matlab/rtsCropMask.m b/tira/matlab/rtsCropMask.m new file mode 100644 index 0000000..0c36960 --- /dev/null +++ b/tira/matlab/rtsCropMask.m @@ -0,0 +1,21 @@ +function new_mask = rtsCropMask(mask, max_pixels) + +%convert to logical +%mask = mask > 0; + +if nnz(mask) < max_pixels + new_mask = mask; +else + j = 0; + for i = 1:length(mask(:)) + if mask(i) > 0 + j = j + 1; + if j == max_pixels + last_pixel = i; + end + end + end + new_mask = zeros(size(mask, 1), size(mask, 2)); + new_mask(1:last_pixel) = mask(1:last_pixel); +end + \ No newline at end of file diff --git a/tira/matlab/rtsEnviClassify.m b/tira/matlab/rtsEnviClassify.m new file mode 100644 index 0000000..3c3dfb3 --- /dev/null +++ b/tira/matlab/rtsEnviClassify.m @@ -0,0 +1,39 @@ +function C = rtsEnviClassify(B, envi_filename, mask, nFeatures) + +% B = TreeBagger class +% nFeatures = number of features cropped out (0 means use all features) + +if nargin < 4 + nFeatures = 0; +end + + +batchSize = 100000; + + + +Ns = nnz(mask) %get the number of samples to be classified +Nb = ceil(Ns / batchSize); %get the number of batches + +C(Ns, 1) = char(0); + +%open the ENVI file +envi = rtsEnviOpen(envi_filename, [envi_filename '.hdr'], mask); + +for b = 1:Nb + + %load a batch + batch = rtsEnviRead(envi, batchSize); + + %crop out the number of features specified (if it is specified) + if nFeatures ~= 0 + batch = batch(1:nFeatures, :); + end + + %classify the batch + C( (b-1)*batchSize + 1 : (b-1)*batchSize + size(batch, 2) ) = cell2mat(predict(B, batch')); + + disp([num2str( round( b / Nb * 100 ) ) '%']); +end + +rtsEnviClose(envi); \ No newline at end of file diff --git a/tira/matlab/rtsEnviClose.m b/tira/matlab/rtsEnviClose.m new file mode 100644 index 0000000..3ee3283 --- /dev/null +++ b/tira/matlab/rtsEnviClose.m @@ -0,0 +1,3 @@ +function rtsEnviClose(enviFID) + +fclose(enviFID.fid); \ No newline at end of file diff --git a/tira/matlab/rtsEnviID.m b/tira/matlab/rtsEnviID.m new file mode 100644 index 0000000..10f559e --- /dev/null +++ b/tira/matlab/rtsEnviID.m @@ -0,0 +1,8 @@ +classdef rtsEnviID < handle + properties + fid + header + mask + fpos + end +end \ No newline at end of file diff --git a/tira/matlab/rtsEnviLoadHeader.m b/tira/matlab/rtsEnviLoadHeader.m new file mode 100644 index 0000000..839a9e4 --- /dev/null +++ b/tira/matlab/rtsEnviLoadHeader.m @@ -0,0 +1,78 @@ +function s = rtsEnviLoadHeader(filename) +%create a cell array of fields +s = struct; + +fid = fopen(filename, 'r', 'n', 'US-ASCII'); + +%read the first field and make sure it is "ENVI" +fname = GetFieldName(fid); +if strcmp(fname, 'ENVI') == 0 + disp('Not an ENVI header file'); + return; +end + +while feof(fid) == 0 + fname = GetFieldName(fid); + if feof(fid) == 1 + return; + end + [value, valid] = ReadField(fid, fname); + if valid == 1 + s = setfield(s, fname, value); + end +end +fclose(fid); + +function t = GetFieldName(fid) +string_struct = textscan(fid, '%s', 1, 'Delimiter', '='); +if feof(fid) == 1 + t = []; + return; +end +t = string_struct{1}{1}; +t = strtrim(t); +t(t==' ') = '_'; + +function [v, valid] = ReadField(fid, field_name) +valid = 1; +stringFields = {'file_type', 'interleave', 'sensor_type', 'wavelength_units'}; +intFields = {'samples', 'lines', 'bands', 'header_offset', 'data_type', 'byte_order'}; + +%if the field is "description", read between the brackets +if strcmp(field_name, 'description') == 1 + textscan(fid, '%[{]', 1); + string_struct = textscan(fid, '%[^}]', 1, 'Whitespace', ''); + textscan(fid, '%[}]', 1); + v = string_struct{1}{1}; + v = strtrim(v); + return; +end +if max(strcmp(field_name, intFields)) ~= 0 + v = fscanf(fid, '%d'); + return; +end +if max(strcmp(field_name, stringFields)) ~= 0 + string_struct = textscan(fid, '%s', 1, 'Whitespace', '\n'); + v = string_struct{1}{1}; + v = strtrim(v); + return; +end + +%read and return the wavelength values +if strcmp(field_name, 'wavelength') == 1 + v = []; + textscan(fid, '%[{]', 1); + c = ' '; + while c ~= '}' + new = fscanf(fid, '%f'); + v = [v new]; + c = fscanf(fid, '%c', 1); + end + return; +end + +%if it doesn't match anything, just read until the end of the line +%string_struct = textscan(fid, '%s', 1, 'Whitespace', '\n'); +string_struct = textscan(fid, '%s', 1, 'Delimiter', '\n'); +v = ''; +valid = 0; diff --git a/tira/matlab/rtsEnviLoadImage.m b/tira/matlab/rtsEnviLoadImage.m new file mode 100644 index 0000000..dd6510d --- /dev/null +++ b/tira/matlab/rtsEnviLoadImage.m @@ -0,0 +1,44 @@ +function [image, header] = rtsEnviLoadImage(filename, headername) +%enter the file name (no .hdr extension) + +header_file = headername; +header = rtsEnviLoadHeader(header_file); + +if strcmp(header.interleave, 'bsq') == 1 + image = zeros(header.samples, header.lines, header.bands); +end +if strcmp(header.interleave, 'bip') == 1 + image = zeros(header.bands, header.samples, header.lines); +end +if strcmp(header.interleave, 'bil') == 1 + image = zeros(header.samples, header.bands, header.lines); +end + + +file_bytes = header.samples*header.lines*header.bands; +%check to make sure there is enough memory available +if ispc + [~, sys] = memory; + if sys.PhysicalMemory.Available < file_bytes*header.data_type && strcmp(header.interleave, 'bsq') == 0 + strResponse = input('The data set will require virtual memory for permutation. Continue? (y/n) ', 's'); + if strcmp(strResponse, 'n') == 1 + return; + end + end +else + disp('Sorry, I can''t check available memory for this OS. Keep your fingers crossed!'); +end + +fid = fopen(filename); +%skip the header - start reading header_offset bytes from the beginning +fseek(fid, header.header_offset, 'bof'); + +%read the data +image(:) = fread(fid, file_bytes, 'float32'); + +if strcmp(header.interleave, 'bip') == 1 + image = permute(image, [2 3 1]); +end +if strcmp(header.interleave, 'bil') == 1 + image = permute(image, [1 3 2]); +end \ No newline at end of file diff --git a/tira/matlab/rtsEnviLoadTraining.m b/tira/matlab/rtsEnviLoadTraining.m new file mode 100644 index 0000000..1788a08 --- /dev/null +++ b/tira/matlab/rtsEnviLoadTraining.m @@ -0,0 +1,57 @@ +function [F, C] = rtsEnviLoadTraining(filenames, classnames, binfile, ppc) + +Nc = length(filenames); %calculate the number of classes + +if nargin < 3 + ppc = 0; +end + +%---------Load the Masks----------- +%first, get the total number of pixels +Ns = 0; +for c = 1:Nc + %load the mask + image = imread(filenames{c}); + + if c == 1 + mask = boolean(zeros(size(image, 2), size(image, 1), Nc)); + end + + if ppc ~= 0 + mask(:, :, c) = rtsRandomizeMask(image(:, :, 1)' > 0, ppc); + else + mask(:, :, c) = image(:, :, 1)' > 0; + end + +end + +%calculate the number of samples +Ns = Ns + nnz(mask); + +%-----------Load the Training Data------------- +disp(['loading ' num2str(Ns) ' samples']); +ci = 1; +for c = 1:Nc + + disp(['loading class ' classnames(c) '...']); + batch = nnz(mask(:, :, c)); + %create an ENVI file object + envi = rtsEnviOpen(binfile, [binfile '.hdr'], mask(:, :, c)); + + if c == 1 + F = zeros(Ns, envi.header.bands); + C(Ns, 1) = char(0); + end + + %read a bunch of spectra + F(ci:ci+batch - 1, :) = rtsEnviRead(envi, batch)'; + C(ci:ci+batch - 1, 1) = repmat([classnames(c)], [batch 1]); + + %update the current index + ci = ci+batch; + + %close the ENVI file + rtsEnviClose(envi); + + disp('done'); +end \ No newline at end of file diff --git a/tira/matlab/rtsEnviOpen.m b/tira/matlab/rtsEnviOpen.m new file mode 100644 index 0000000..badc640 --- /dev/null +++ b/tira/matlab/rtsEnviOpen.m @@ -0,0 +1,35 @@ +function EID = rtsEnviOpen(filename, headername, mask) + +%opens an ENVI file and returns an ID structure +% filename = name of ENVI file +% headername = name of ENVI header +% FID = structure containing file information +% mask = binary image specifying valid spectra + +%assign the mask variable +if(nargin < 3) + mask = 1; +end + +%open the file and save the file ID +fid = fopen(filename, 'r'); + +%open the ENVI header file +header = rtsEnviLoadHeader(headername); + +%this is currently only valid for BIP files +if(~strcmp(header.interleave, 'bip')) + disp('Error: This function only works for BIP interleave files. Load in ENVI and convert.'); +end +if(header.data_type ~= 4) + disp('Error: This function only works for floating-point files.'); +end + +EID = rtsEnviID; +EID.fid = fid; +EID.header = header; +EID.mask = mask; +EID.fpos = 0; + +%EID = struct('fid', fid, 'header', header, 'mask', mask, 'fpos', 0); + diff --git a/tira/matlab/rtsEnviRandomForest2C_apply.m b/tira/matlab/rtsEnviRandomForest2C_apply.m new file mode 100644 index 0000000..e1d2a7c --- /dev/null +++ b/tira/matlab/rtsEnviRandomForest2C_apply.m @@ -0,0 +1,41 @@ +function P = rtsEnviRandomForest2C_apply(RF, EnviFileName, EnviHeaderName, TissueMask, reference) + +%Applies a 2-class random forest classifier to the given image. Only +%pixels specified in TissueMask are classified. The estimated rule data is +%returned as an image sequence. + +%load the tissue mask +maskImage = imread(TissueMask); +maskBinary = (maskImage(:, :, 1) > 0)'; + +nPixels = nnz(maskBinary); +disp(['Number of pixels: ' num2str(nPixels)]); + + +%load the data +disp(['Loading Features: ' EnviFileName]); +tLoadTime = tic; +fid = rtsEnviOpen(EnviFileName, EnviHeaderName, maskBinary); +F = rtsEnviRead(fid, nPixels); +rtsEnviClose(fid); +disp(['Time: ' num2str(toc(tLoadTime)) 's']); + +%transpose +F = F'; + +%apply the reference +if nargin == 5 + Fnorm = repmat(F(:, reference), 1, size(F, 2)); + F = F./Fnorm; + F(:, reference) = []; +end + +%classify the data +disp('Classifying...'); +tClassTime = tic; +T = predict(RF, F); +disp(['Time: ' num2str(toc(tClassTime)) 's']); +%Cl = Cpost > threshold; + +%reconstruct the image from the mask +P = rtsMaskReconstruct(T', maskBinary, -1); \ No newline at end of file diff --git a/tira/matlab/rtsEnviRandomForest2C_train.m b/tira/matlab/rtsEnviRandomForest2C_train.m new file mode 100644 index 0000000..8bf13d7 --- /dev/null +++ b/tira/matlab/rtsEnviRandomForest2C_train.m @@ -0,0 +1,76 @@ +function RF = rtsEnviRandomForest2C_train(EnviFileName, EnviHeaderName, RoiImages, reference) + +%Creates a 2-class random forest classifier for an Envi BIP file using the +%provided ROI images. The resulting classifier uses regression to map +%class A to 1 and class B to 0. This allows the creation of ROC curves. +% +% EnviFilename - Name of an ENVI BIP file +% RoiImages - Cell array containing names of ROI images (masks) + +%default parameters +maxPixels = 200000; +threshold = 0.5; +nTrees = 100; +nThreads = 8; +%trainPixels = 200000; + +%determine the number of classes +nClasses = length(RoiImages); +%make sure that there are only two classes +if nClasses > 2 + disp('This classifier only supports 2 classes.'); + return; +end + +%for each class, load the training data +T = []; +F = []; +for c = 1:nClasses + + %load the class mask + maskImage = imread(RoiImages{c}); + maskBinary = (maskImage(:, :, 1) > 0)'; + + disp('------------------------------------------------------'); + %load epithelium spectra + disp(['Loading Training Class ' num2str(c) ' pixels: ' EnviFileName]); + tLoadTime = tic; + fid = rtsEnviOpen(EnviFileName, EnviHeaderName, maskBinary); + Fc = rtsEnviRead(fid, maxPixels); + rtsEnviClose(fid); + + if c == 1 + Tc = ones(size(Fc, 2), 1); + else + Tc = zeros(size(Fc, 2), 1); + end + + disp(['Time: ' num2str(toc(tLoadTime)) 's']); + + %add features and targets to the final vectors + T = [T; Tc]; + F = [F; Fc']; +end + +%apply the reference +if nargin == 4 + Fnorm = repmat(F(:, reference), 1, size(F, 2)); + F = F./Fnorm; + F(:, reference) = []; +end + +%parallelize if specified +if nThreads > 1 + matlabpool('open', nThreads); + paraoptions = statset('UseParallel', 'always'); +end + +%train the classifier +disp('Creating a Random Forest classifier...'); +tTrainTime = tic; +RF = TreeBagger(nTrees, F, T, 'method', 'regression', 'Options',paraoptions); +disp(['Time: ' num2str(toc(tTrainTime)) 's']); + +if nThreads > 1 + matlabpool close +end \ No newline at end of file diff --git a/tira/matlab/rtsEnviRandomForest2C_validate.m b/tira/matlab/rtsEnviRandomForest2C_validate.m new file mode 100644 index 0000000..626a03c --- /dev/null +++ b/tira/matlab/rtsEnviRandomForest2C_validate.m @@ -0,0 +1,65 @@ +function [Croc, Cauc] = rtsEnviRandomForest2C_validate(RF, EnviFileName, EnviHeaderName, RoiImages, reference) + +%Validates a 2-class random forest classifier, creating ROC curves. + +%parameters +maxPixels = 200000; + +%determine the number of classes +nClasses = length(RoiImages); +%make sure that there are only two classes +if nClasses > 2 + disp('This classifier only supports 2 classes.'); + return; +end + +%for each class, load the validation data +T = []; +F = []; +for c = 1:nClasses + + %load the class mask + maskImage = imread(RoiImages{c}); + maskBinary = (maskImage(:, :, 1) > 0)'; + + disp('------------------------------------------------------'); + %load epithelium spectra + disp(['Loading Validation Class ' num2str(c) ' pixels: ' EnviFileName]); + tLoadTime = tic; + fid = rtsEnviOpen(EnviFileName, EnviHeaderName, maskBinary); + Fc = rtsEnviRead(fid, maxPixels); + rtsEnviClose(fid); + + if c == 1 + Tc = ones(size(Fc, 2), 1); + else + Tc = zeros(size(Fc, 2), 1); + end + + disp(['Time: ' num2str(toc(tLoadTime)) 's']); + + %add features and targets to the final vectors + T = [T; Tc]; + F = [F; Fc']; +end + +%apply the reference +if nargin == 5 + Fnorm = repmat(F(:, reference), 1, size(F, 2)); + F = F./Fnorm; + F(:, reference) = []; +end + +%classify the data (get the estimated posterior probability) +disp('Validating...'); +tClassTime = tic; +Tpost = predict(RF, F); +disp(['Time: ' num2str(toc(tClassTime)) 's']); + +%calculate and display the ROC curve +disp('Calculating ROC...'); +tRocTime = tic; +[Croc, Cauc, threshList] = rtsROC(Tpost, T); +disp(['Time: ' num2str(toc(tRocTime)) 's']); +disp(['AUC = ' num2str(Cauc)]); +plot(Croc(:, 1), Croc(:, 2)); \ No newline at end of file diff --git a/tira/matlab/rtsEnviRead.m b/tira/matlab/rtsEnviRead.m new file mode 100644 index 0000000..d46db6c --- /dev/null +++ b/tira/matlab/rtsEnviRead.m @@ -0,0 +1,68 @@ +function S = rtsEnviRead(fid, batchSize) + +%This function reads a batch of spectra from the given ENVI file ID +% fid = ENVI file ID created using rtsEnviOpen +% batchSize = number of spectra to read + +%if there is no mask, just load each spectrum in order +if(fid.mask == 1) + + %compute the new batch size in case we are near the eof + nSpectra = fid.header.samples * fid.header.lines; + remainingSpectra = nSpectra - fid.fpos; + + if(batchSize > remainingSpectra) + batchSize = remainingSpectra; + end + + S = fread(fid.fid, [fid.header.bands batchSize], '*float32'); + + %increment the fid counter + fid.fpos = fid.fpos + batchSize; + +%otherwise only load valid spectra +else + + %compute the new batch size in case we are near the eof + if(fid.fpos == 0) + remainingSpectra = nnz(fid.mask); + else + nSpectra = nnz(fid.mask); + maskRead = fid.mask(1:fid.fpos+1); + remainingSpectra = nSpectra - nnz(maskRead); + end + + if(batchSize > remainingSpectra) + batchSize = remainingSpectra; + end + + %allocate space for the spectra + S = zeros(fid.header.bands, batchSize); + + %for each spectrum in the batch + for s = 1:batchSize + + %while the current spectrum is invalid + skip = 0; + while (~fid.mask(fid.fpos + 1)) + %read the invalid spectrum + %invalid = fread(fid.fid, [fid.header.bands 1], '*float32'); + skip = skip + 1; + + %increment the file position + fid.fpos = fid.fpos + 1; + end + fseek(fid.fid, skip * fid.header.bands * 4, 'cof'); + + test = fread(fid.fid, [fid.header.bands 1], '*float32'); + if size(test) ~= size(S(:, s)) + size(test) + size(S(:, s)) + end + S(:, s) = test; + fid.fpos = fid.fpos + 1; + + end + +end + diff --git a/tira/matlab/rtsEnviSaveImage.m b/tira/matlab/rtsEnviSaveImage.m new file mode 100644 index 0000000..a22402b --- /dev/null +++ b/tira/matlab/rtsEnviSaveImage.m @@ -0,0 +1,41 @@ +function rtsEnviSaveImage(S, fileName, headerName, W) + +% S = 3D hyperspectral data +% W = list of wavelength values + +%save the image +rtsSaveRAW(S, fileName, 'float32', 'l'); + +%create the header file +headerFile = [fileName '.hdr']; +if nargin == 3 + headerFile = headerName; +end + +fid = fopen(headerFile, 'w'); + +%write the header and description +fprintf(fid, 'ENVI\n'); +fprintf(fid, 'description = {\n'); +fprintf(fid, ' File Saved using Matlab.}\n'); +fprintf(fid, 'samples = %d\n', size(S, 1)); +fprintf(fid, 'lines = %d\n', size(S, 2)); +fprintf(fid, 'bands = %d\n', size(S, 3)); +fprintf(fid, 'header offset = 0\n'); +fprintf(fid, 'file type = ENVI Standard\n'); +fprintf(fid, 'data type = 4\n'); +fprintf(fid, 'interleave = bsq\n'); +fprintf(fid, 'sensor type = Unknown\n'); +fprintf(fid, 'byte order = 0\n'); + +if nargin == 4 + %output wavelength units + fprintf(fid, 'wavelength = {\n'); + for i = 1:length(W)-1 + fprintf(fid, ' %f,\n', W(i)); + end + fprintf(fid, ' %f\n', W(length(W))); + fprintf(fid, '}\n'); +end + +fclose(fid); diff --git a/tira/matlab/rtsGaussianBlurND.m b/tira/matlab/rtsGaussianBlurND.m new file mode 100644 index 0000000..549d15c --- /dev/null +++ b/tira/matlab/rtsGaussianBlurND.m @@ -0,0 +1,30 @@ +function R = rtsGaussianBlurND(input, sigma) +%This function convolves an ND array with a gaussian kernel specified by +%sigma. This takes advantage of the separability of the kernel and is +%therefore very fast. + +%disp('begin***********************************************'); + +%get the number of dimensions for the convolution +dim = ndims(input); + +%initialize the result to the source +R = input; + + +for d=1:dim + %if the dimension is greater than 1 and the sigma not a dirac + if size(input, d) > 1 && sigma(d) > 0 + %create a 1D kernel + kernelshape = ones(1, dim); + kernelshape(d) = sigma(d)*6; + kernel = zeros(kernelshape); + kernel(:) = ndgauss(sigma(d)*6, sigma(d)); + + %perform the convolution + R = convn(R, kernel, 'same'); + end +end + + + \ No newline at end of file diff --git a/tira/matlab/rtsHSVtoRGB.m b/tira/matlab/rtsHSVtoRGB.m new file mode 100644 index 0000000..6524ea9 --- /dev/null +++ b/tira/matlab/rtsHSVtoRGB.m @@ -0,0 +1,29 @@ +function RGBval = rtsHSVtoRGB(HSVval) + +H = HSVval(1); +S = HSVval(2); +V = HSVval(3); + +C = V*S; +Hprime = H/60; +X = C*(1 - abs(mod(Hprime, 2) - 1)); + +RGBval = [0 0 0]; +if Hprime >= 0 && Hprime < 1 + RGBval = [C X 0]; +end +if Hprime >= 1 && Hprime < 2 + RGBval = [X C 0]; +end +if Hprime >= 2 && Hprime < 3 + RGBval = [0 C X]; +end +if Hprime >= 3 && Hprime < 4 + RGBval = [0 X C]; +end +if Hprime >= 4 && Hprime < 5 + RGBval = [X 0 C]; +end +if Hprime >= 5 && Hprime < 6 + RGBval = [C 0 X]; +end \ No newline at end of file diff --git a/tira/matlab/rtsInt2Str.m b/tira/matlab/rtsInt2Str.m new file mode 100644 index 0000000..ac1d025 --- /dev/null +++ b/tira/matlab/rtsInt2Str.m @@ -0,0 +1,20 @@ +function result = rtsInt2Str(num, digits) + + +%get the number of digits in the current number +temp = num; +current_digits = 0; +while temp >= 1 + temp = temp/10; + current_digits = current_digits+1; +end + +if current_digits > digits + result = int2str(num); +else + result = []; + for i = 1:digits - current_digits + result = [result '0']; + end + result = [result int2str(num)]; +end \ No newline at end of file diff --git a/tira/matlab/rtsLoadImageStack.m b/tira/matlab/rtsLoadImageStack.m new file mode 100644 index 0000000..9a8c3b9 --- /dev/null +++ b/tira/matlab/rtsLoadImageStack.m @@ -0,0 +1,33 @@ +function R = rtsLoadImageStack(directoryName) + +fileList = dir(directoryName); +fileList = fileList(3:length(fileList)); + +nFiles = length(fileList); + +%enable the progress bar +gui_active(1); +h = progressbar([], 0, ['Loading ' num2str(nFiles) ' Images...'], 'Load Stack'); + + +%load each file into a volume +for i=1:nFiles + fileName = [directoryName '\' fileList(i).name]; + image = imread(fileName); + + %allocate space + if i == 1 + R = zeros(size(image, 1), size(image, 2), nFiles, 'uint8'); + end + R(:, :, i) = image(:, :, 1); + + h = progressbar(h, 1/(nFiles)); + if ~gui_active + progressbar(h, -1); + break; + end +end + +progressbar(h, -1); + + diff --git a/tira/matlab/rtsMaskReconstruct.m b/tira/matlab/rtsMaskReconstruct.m new file mode 100644 index 0000000..eed102e --- /dev/null +++ b/tira/matlab/rtsMaskReconstruct.m @@ -0,0 +1,35 @@ +function I = rtsMaskReconstruct(data, mask, fillVal) + +%This function reconstructs an array of data created from a mask +% data = a VxD matrix, where D = positions and V = values +% mask = a binary image with D nonzero values + +%compute the number of values at each pixel +nV = size(data, 1); + +%allocate space +nX = size(mask, 1); +nY = size(mask, 2); +I = ones(nV, nX*nY) * fillVal; + +d=1; +%for each position +for p = 1:nX*nY + + %if the mask value at the position is true, copy values + if(mask(p) && d < size(data, 2)) + I(:, p) = data(:, d); + + %increment the data pointer + d = d + 1; + + %if the mask value at the position is zero, set all values to zero + else + I(:, p) = ones(nV, 1) * fillVal; + end + +end + +%reshape the array into an image +I = reshape(I', [nX nY nV]); + diff --git a/tira/matlab/rtsMonteCarloSphere.m b/tira/matlab/rtsMonteCarloSphere.m new file mode 100644 index 0000000..c749360 --- /dev/null +++ b/tira/matlab/rtsMonteCarloSphere.m @@ -0,0 +1,29 @@ +function S = rtsMonteCarloSphere(sqrtN, startAngle, endAngle) + +%allocate space for the samples +S = zeros(sqrtN^2, 2); + +if sqrtN == 1 + S = [0 0]; + return; +end +cosStart = cos(startAngle); +cosEnd = cos(endAngle); + +i=1; % array index +oneoverN = 1.0/sqrtN; +for a = 0:sqrtN-1 + for b = 0:sqrtN-1 + %generate unbiased distribution of spherical coords + U1 = rand(1, 1); + U2 = rand(1, 1); + %x = 1.0 - (a + U1) * oneoverN * (1.0 - cosEnd) + x = cosStart - (a + U1) * oneoverN * (1.0 - cosEnd); + y = (b + U2) * oneoverN; + theta = acos(x); + phi = 2.0 * pi * y; + S(i, 1) = theta; + S(i, 2) = phi; + i = i+1; + end +end diff --git a/tira/matlab/rtsOffsetPlots.m b/tira/matlab/rtsOffsetPlots.m new file mode 100644 index 0000000..eebc22e --- /dev/null +++ b/tira/matlab/rtsOffsetPlots.m @@ -0,0 +1,11 @@ +function R = rtsOffsetPlots(D, s) +%This function offsets a series of plots in D by intervals of s +%R = the resulting offset data +%D = the data as a 2D matrix +%s = the interval used as an offset + + nPlots = size(D, 2); + + offsetMat = repmat(1:nPlots, size(D, 1), 1) * s; + + R = D + offsetMat; \ No newline at end of file diff --git a/tira/matlab/rtsRAW2Images.m b/tira/matlab/rtsRAW2Images.m new file mode 100644 index 0000000..334f360 --- /dev/null +++ b/tira/matlab/rtsRAW2Images.m @@ -0,0 +1,8 @@ +function rtsRAW2Images(source, destination_directory) + +for i=1:size(source, 3) + filename = [destination_directory '\' rtsInt2Str(i, 4) '.bmp']; + imwrite(source(:, :, i)', filename); + disp(filename); +end + \ No newline at end of file diff --git a/tira/matlab/rtsROC.m b/tira/matlab/rtsROC.m new file mode 100644 index 0000000..82721b1 --- /dev/null +++ b/tira/matlab/rtsROC.m @@ -0,0 +1,35 @@ +function [ROC, AUC, T] = rtsROC(thresholds, class) + +%Computes the ROC curves given a set of thresholds and class assignments +% class = boolean array describing actual class membership +% thresholds = threshold values for class assignment +% ROC = ROC curve as column vectors (1 = 1-specificity, 2 = sensitivity) +% AUC = area under the ROC curve computed using the trapezoid method +% T = threshold values associated with each 1-specificity value + +nValues = length(thresholds); +ROC = zeros(nValues, 2); + +%sort both arrays +[T, ix] = sort(thresholds, 'descend'); +C = class(ix); + +%compute the necessary global values +P = nnz(class); +N = nValues - P; + +for i = 1:nValues + TP = nnz(C(1:i)); + sensitivity = TP/P; + + FP = i - TP; + FPR = FP/N; + specificity = 1 - FPR; + + ROC(i, 1) = 1 - specificity; + ROC(i, 2) = sensitivity; +end + +AUC = trapz(ROC(:, 1), ROC(:, 2)); + + \ No newline at end of file diff --git a/tira/matlab/rtsRandomizeMask.m b/tira/matlab/rtsRandomizeMask.m new file mode 100644 index 0000000..62866d3 --- /dev/null +++ b/tira/matlab/rtsRandomizeMask.m @@ -0,0 +1,22 @@ +function R = rtsRandomizeMask(mask, N) + +%error checking +if N > nnz(mask) + N = nnz(mask); +end + +if N < 0 + N = 0; +end + +%get the indices of all nonzero values in the mask +ind = find(mask); + +%randomize the indices +rind = ind(randperm(size(ind, 1))); + +%create the new randomized mask (random subset of the old mask) +R = zeros(size(mask)); +R(rind(1:N)) = 1; + +R = R > 0; \ No newline at end of file diff --git a/tira/matlab/rtsSaveRAW.m b/tira/matlab/rtsSaveRAW.m new file mode 100644 index 0000000..a6745c1 --- /dev/null +++ b/tira/matlab/rtsSaveRAW.m @@ -0,0 +1,11 @@ +function rtsSaveRAW(data, filename, type, endian) +%Loads a RAW image file. +%LoadRAW(filename, x, y, z, c, type) returns a [CxXxYxZ] array representing +%RAW data loaded from disk. 'type' defines the data type to be loaded (ex. +%'uint8'. 'endian' defines the byte order: 'b' = big endian, 'l' = little +%endian. + +file_id = fopen(filename, 'w', endian); +vol = zeros(size(data, 1), size(data, 2), size(data, 3), size(data, 4)); +vol(:) = fwrite(file_id, data, type); +fclose(file_id); \ No newline at end of file diff --git a/tira/matlab/stimBrewerColormap.m b/tira/matlab/stimBrewerColormap.m new file mode 100644 index 0000000..0a2c521 --- /dev/null +++ b/tira/matlab/stimBrewerColormap.m @@ -0,0 +1,28 @@ +function result = stimBrewerColormap(R) + +%returns a Brewer colormap with the specified resolution R + +ctrlPts = zeros(11, 3); + +ctrlPts(1, :) = [0.192157, 0.211765, 0.584314]; +ctrlPts(2, :) = [0.270588, 0.458824, 0.705882]; +ctrlPts(3, :) = [0.454902, 0.678431, 0.819608]; +ctrlPts(4, :) = [0.670588, 0.85098, 0.913725]; +ctrlPts(5, :) = [0.878431, 0.952941, 0.972549]; +ctrlPts(6, :) = [1, 1, 0.74902]; +ctrlPts(7, :) = [0.996078, 0.878431, 0.564706]; +ctrlPts(8, :) = [0.992157, 0.682353, 0.380392]; +ctrlPts(9, :) = [0.956863, 0.427451, 0.262745]; +ctrlPts(10, :) = [0.843137, 0.188235, 0.152941]; +ctrlPts(11, :) = [0.647059, 0, 0.14902]; + +X = 1:11; + +r = 1:11/R:11; + +R = interp1(X, ctrlPts(:, 1), r); +G = interp1(X, ctrlPts(:, 2), r); +B = interp1(X, ctrlPts(:, 3), r); + +result = [R' G' B']; + diff --git a/tira/optics/beam.h b/tira/optics/beam.h new file mode 100644 index 0000000..b82c158 --- /dev/null +++ b/tira/optics/beam.h @@ -0,0 +1,98 @@ +#ifndef TIRA_BEAM +#define TIRA_BEAM + +#include "../math/vec3.h" +#include "../math/function.h" +#include "../optics/planewave.h" +#include +#include +#include + +namespace tira{ + namespace optics{ +template +class beam { + + vec3 m_direction; + vec3 m_focus; + cvec3 m_E; + T m_k; + + + +public: + ///

+ /// Beam constructor + /// + /// Propagation direction of the beam (automatically normalized). + /// Focal point for the beam. + /// Beam polarization (automatically normalized and orthogonalized). Only works for linear polarization, though. + /// Complex beam amplitude (applied to the polarization to create a linearly polarized beam). + /// Wavelength + beam( + vec3 d = vec3(0, 0, 1), + vec3 f = vec3(0, 0, 0), + vec3 pol = vec3(1, 0, 0), + std::complex E = std::complex(1.0, 0.0), + T lambda = 1.0 + ) { + + m_direction = d.direction(); //normalize and set the plane wave direction + pol = (pol - d.dot(pol)).direction(); //orthogonalize and normalize the polarization vector + m_focus = f; //set the focal point + m_E[0] = pol[0] * E; //convert the polarization and complex amplitude to a linearly-polarized E vector + m_E[1] = pol[1] * E; + m_E[2] = pol[2] * E; + m_k = 2.0 * std::numbers::pi * lambda; //calculate and store the wavenumber + } + + /// + /// Generate a focal spot for the beam using Monte-Carlo sampling. + /// + /// Numerical aperture of the focusing optics. + /// Number of Monte-Carlo samples. + /// NA of the internal obscuration. + /// + std::vector< planewave > mcfocus(T NA, size_t samples, T NA_in = 0) { + + //create a rotation matrix to orient the beam along the specified direction vector + tira::matrix_sq R;// = tira::matrix_sq::identity(); //initialize the rotation matrix to the identity matrix + tira::vec3 Z(0, 0, 1); //create a vector along the Z direction + tira::quaternion q; + q.CreateRotation(Z, m_direction); + R = q.toMatrix3(); + + std::vector< planewave > result(samples); //generate an array of N plane waves + double alpha = std::asin(NA); //calculate the angle of the band-pass + double alpha_in = std::asin(NA_in); //calculate the angle of the central obscuration + double cos_alpha = std::cos(alpha); //calculate the cosine of the band-pass and central obscuration + double cos_alpha_in = std::cos(alpha_in); + + //random sampling is done using Archimede's method - uniform samples are taken in cylindrical space and projected onto a sphere + std::random_device rd; //create a random number generator + std::default_random_engine eng(rd()); + std::uniform_real_distribution<> theta_dist(0.0, 2.0 * std::numbers::pi); //create a distribution for theta (in cylindrical coordinates) + std::uniform_real_distribution<> z_dist(cos_alpha, cos_alpha_in); //create a uniform distribution for sampling the z-axis + + //generate a guide plane wave + planewave pw(m_direction[0] * m_k, m_direction[1] * m_k, m_direction[2] * m_k, m_E[0], m_E[1], m_E[2]); + + double theta, phi; //variables to store the coordinates for theta/phi on a sphere + tira::vec3 dir; + T w = 1.0 / samples; //integration weight + for (size_t n = 0; n < samples; n++) { + theta = theta_dist(eng); //randomly generate a theta coordinate between 0 and 2pi + phi = std::acos(z_dist(eng)); //randomly generate a z coordinate based on the NA and project to phi + dir = R * tira::vec3(1.0, theta, phi).sph2cart(); //convert the value to Cartesian coordinates and store the sample vector + result[n] = pw.refract(dir); //refract the plane wave to align with the sample direction + result[n] = result[n].translate(m_focus[0], m_focus[1], m_focus[2]); //refocus the plane wave to the focal position + result[n] = result[n] * w; //apply the integration weight (1/N) + } + + return result; //return the sample array + } +}; +} //end namespace optics +} //end namespace tira + +#endif diff --git a/tira/optics/lens.h b/tira/optics/lens.h new file mode 100644 index 0000000..0e0dfcf --- /dev/null +++ b/tira/optics/lens.h @@ -0,0 +1,148 @@ +#ifndef STIM_LENS_H +#define STIM_LENS_H + +#include "scalarwave.h" +#include "../math/bessel.h" +#include "../cuda/cudatools/devices.h" +#include "../visualization/colormap.h" +#include "../math/fft.h" + +#include "cufft.h" + +#include + +namespace stim{ + + /// Perform a k-space transform of a scalar field (FFT). The given field has a width of x and the calculated momentum space has a + /// width of kx (in radians). + /// @param K is a pointer to the output array of all plane waves in the field + /// @param kx is the width of the frame in momentum space + /// @param ky is the height of the frame in momentum space + /// @param E is the field to be transformed + /// @param x is the width of the field in the spatial domain + /// @param y is the height of the field in the spatial domain + /// @param nx is the number of pixels representing the field in the x (and kx) direction + /// @param ny is the number of pixels representing the field in the y (and ky) direction + template + void cpu_scalar_to_kspace(stim::complex* K, T& kx, T& ky, stim::complex* E, T x, T y, size_t nx, size_t ny){ + + kx = stim::TAU * nx / x; //calculate the width of the momentum space + ky = stim::TAU * ny / y; + + stim::complex* dev_FFT; + HANDLE_ERROR( cudaMalloc(&dev_FFT, sizeof(stim::complex) * nx * ny) ); //allocate space on the CUDA device for the output array + + stim::complex* dev_E; + HANDLE_ERROR( cudaMalloc(&dev_E, sizeof(stim::complex) * nx * ny) ); //allocate space for the field + HANDLE_ERROR( cudaMemcpy(dev_E, E, sizeof(stim::complex) * nx * ny, cudaMemcpyHostToDevice) ); //copy the field to GPU memory + + cufftResult result; + cufftHandle plan; + result = cufftPlan2d(&plan, nx, ny, CUFFT_C2C); + if(result != CUFFT_SUCCESS){ + std::cout<<"Error creating cuFFT plan."<* fft = (stim::complex*) malloc(sizeof(stim::complex) * nx * ny); + HANDLE_ERROR( cudaMemcpy(fft, dev_FFT, sizeof(stim::complex) * nx * ny, cudaMemcpyDeviceToHost) ); + + stim::cpu_fftshift(K, fft, nx, ny); + } + + template + void cpu_scalar_from_kspace(stim::complex* E, T& x, T& y, stim::complex* K, T kx, T ky, size_t nx, size_t ny){ + + x = stim::TAU * nx / kx; //calculate the width of the momentum space + y = stim::TAU * ny / ky; + + stim::complex* fft = (stim::complex*) malloc(sizeof(stim::complex) * nx * ny); + stim::cpu_ifftshift(fft, K, nx, ny); + + stim::complex* dev_FFT; + HANDLE_ERROR( cudaMalloc(&dev_FFT, sizeof(stim::complex) * nx * ny) ); //allocate space on the CUDA device for the output array + HANDLE_ERROR( cudaMemcpy(dev_FFT, fft, sizeof(stim::complex) * nx * ny, cudaMemcpyHostToDevice) ); //copy the field to GPU memory + + stim::complex* dev_E; + HANDLE_ERROR( cudaMalloc(&dev_E, sizeof(stim::complex) * nx * ny) ); //allocate space for the field + + cufftResult result; + cufftHandle plan; + result = cufftPlan2d(&plan, nx, ny, CUFFT_C2C); + if(result != CUFFT_SUCCESS){ + std::cout<<"Error creating cuFFT plan."<) * nx * ny, cudaMemcpyDeviceToHost) ); + + + } + + /// Propagate a field slice along its orthogonal direction by a given distance z + /// @param Enew is the resulting propogated field + template + void cpu_scalar_propagate(stim::complex* Enew, stim::complex* E, T sx, T sy, T z, T k, size_t nx, size_t ny){ + + stim::complex* K = (stim::complex*) malloc( sizeof(stim::complex) * nx * ny ); + + T Kx, Ky; //width and height in k space + cpu_scalar_to_kspace(K, Kx, Ky, E ,sx, sy, nx, ny); + + T* mag = (T*) malloc( sizeof(T) * nx * ny ); + stim::abs(mag, K, nx * ny); + stim::cpu2image(mag, "kspace_pre_shift.bmp", nx, ny, stim::cmBrewer); + + size_t kxi, kyi; + size_t i; + T kx, kx_sq, ky, ky_sq, k_sq; + T kz; + stim::complex shift; + T min_kx = -Kx / 2; + T dkx = Kx / (nx); + T min_ky = -Ky / 2; + T dky = Ky / (ny); + for(kyi = 0; kyi < ny; kyi++){ //for each plane wave in the ky direction + for(kxi = 0; kxi < nx; kxi++){ //for each plane wave in the ky direction + i = kyi * nx + kxi; + + kx = min_kx + kxi * dkx; //calculate the position of the current plane wave + ky = min_ky + kyi * dky; + + kx_sq = kx * kx; + ky_sq = ky * ky; + k_sq = k*k; + + if(kx_sq + ky_sq < k_sq){ + kz = sqrt(k*k - kx * kx - ky * ky); //estimate kz using the Fresnel approximation + shift = -exp(stim::complex(0, kz * z)); + K[i] *= shift; + } + else{ + K[i] = 0; + } + } + } + + stim::abs(mag, K, nx * ny); + stim::cpu2image(mag, "kspace_post_shift.bmp", nx, ny, stim::cmBrewer); + + cpu_scalar_from_kspace(Enew, sx, sy, K, Kx, Ky, nx, ny); + } + +} + + +#endif \ No newline at end of file diff --git a/tira/optics/planewave.h b/tira/optics/planewave.h new file mode 100644 index 0000000..1832458 --- /dev/null +++ b/tira/optics/planewave.h @@ -0,0 +1,421 @@ +#ifndef TIRA_PLANEWAVE_H +#define TIRA_PLANEWAVE_H + +#include +#include +#include + +#include "../math/vec3.h" +#include "../math/quaternion.h" +#include "../math/constants.h" +#include "../math/plane.h" +#include + + +namespace tira{ + namespace optics{ + + + /// evaluate the scalar field produced by a plane wave at a point (x, y, z) + + /// @param x is the x-coordinate of the point + /// @param y is the y-coordinate of the point + /// @param z is the z-coordinate of the point + /// @param A is the amplitude of the plane wave, specifically the field at (0, 0, 0) + /// @param kx is the k-vector component in the x direction + /// @param ky is the k-vector component in the y direction + /// @param kz is the k-vector component in the z direction + template + std::complex planewave_scalar(T x, T y, T z, std::complex A, T kx, T ky, T kz){ + T d = x * kx + y * ky + z * kz; //calculate the dot product between k and p = (x, y, z) to find the distance p is along the propagation direction + std::complex di = std::complex(0, d); //calculate the phase shift that will have to be applied to propagate the wave distance d + return A * exp(di); //multiply the phase term by the amplitude at (0, 0, 0) to propagate the wave to p + } + + /// evaluate the scalar field produced by a plane wave at several positions + + /// @param field is a pre-allocated block of memory that will store the complex field at all points + /// @param N is the number of field values to be evaluated + /// @param x is a set of x coordinates defining positions within the field (NULL implies that all values are zero) + /// @param y is a set of y coordinates defining positions within the field (NULL implies that all values are zero) + /// @param z is a set of z coordinates defining positions within the field (NULL implies that all values are zero) + /// @param A is the amplitude of the plane wave, specifically the field at (0, 0, 0) + /// @param kx is the k-vector component in the x direction + /// @param ky is the k-vector component in the y direction + /// @param kz is the k-vector component in the z direction + template + void cpu_planewave_scalar(std::complex* field, size_t N, T* x, T* y = NULL, T* z = NULL, std::complex A = 1.0, T kx = 0.0, T ky = 0.0, T kz = 0.0){ + T px, py, pz; + for(size_t i = 0; i < N; i++){ // for each element in the array + (x == NULL) ? px = 0 : px = x[i]; // test for NULL values + (y == NULL) ? py = 0 : py = y[i]; + (z == NULL) ? pz = 0 : pz = z[i]; + + field[i] = planewave_scalar(px, py, pz, A, kx, ky, kz); // call the single-value plane wave function + } + } + + +template +class planewave{ + +protected: + + cvec3 m_k; //k-vector, pointed in propagation direction with magnitude |k| = tau / lambda = 2pi / lambda + cvec3 m_E; //amplitude (for a scalar plane wave, only E0[0] is used) + + /// Bend a plane wave via refraction, given that the new propagation direction is known + CUDA_CALLABLE planewave bend(vec3 v) const { + + vec3 k_real(m_k.get(0).real(), m_k.get(1).real(), m_k.get(2).real()); //force the vector to be real (can only refract real directions) + + vec3 kn_hat = v.direction(); //normalize the new k + vec3 k_hat = k_real.direction(); //normalize the current k + + planewave new_p; //create a new plane wave + + T k_dot_kn = k_hat.dot(kn_hat); //if kn is equal to k or -k, handle the degenerate case + + //if k . n < 0, then the bend is a reflection + if(k_dot_kn < 0) k_hat = -k_hat; //flip k_hat + + if(k_dot_kn == -1){ + new_p.m_k = -m_k; + new_p.m_E = m_E; + return new_p; + } + else if(k_dot_kn == 1){ + new_p.m_k = m_k; + new_p.m_E = m_E; + return new_p; + } + + vec3 r = k_hat.cross(kn_hat); //compute the rotation vector + T theta = asin(r.len()); //compute the angle of the rotation about r + quaternion q; //create a quaternion to capture the rotation + q.CreateRotation(theta, r.direction()); + matrix_sq R = q.toMatrix3(); + vec3< std::complex > E(m_E.get(0), m_E.get(1), m_E.get(2)); + vec3< std::complex > E0n = R * E; //apply the rotation to E0 + //new_p.m_k = kn_hat * kmag(); + //new_p.m_E = E0n; + new_p.m_k[0] = kn_hat[0] * kmag(); + new_p.m_k[1] = kn_hat[1] * kmag(); + new_p.m_k[2] = kn_hat[2] * kmag(); + + new_p.m_E[0] = E0n[0]; + new_p.m_E[1] = E0n[1]; + new_p.m_E[2] = E0n[2]; + + + return new_p; + } + +public: + + + + ///constructor: create a plane wave propagating along k + CUDA_CALLABLE planewave(std::complex kx, std::complex ky, std::complex kz, + std::complex Ex, std::complex Ey, std::complex Ez) { + + m_k = cvec3(kx, ky, kz); + m_E = cvec3(Ex, Ey, Ez); + force_orthogonal(); + } + + CUDA_CALLABLE planewave() : planewave(0, 0, 1, 1, 0, 0) {} + + //copy constructor + CUDA_CALLABLE planewave(const planewave& other) { + m_k = other.m_k; + m_E = other.m_E; + } + + /// Assignment operator + CUDA_CALLABLE planewave& operator=(const planewave& rhs) { + m_k = rhs.m_k; + m_E = rhs.m_E; + + return *this; + } + + /// Forces the k and E vectors to be orthogonal + CUDA_CALLABLE void force_orthogonal() { + + /*if (m_E.norm2() == 0) return; + + cvec3 k_dir = m_k.direction(); //calculate the normalized direction vectors for k and E + cvec3 E_dir = m_E.direction(); + cvec3 side = k_dir.cross(E_dir); //calculate a side vector for projection + cvec3 side_dir = side.direction(); //normalize the side vector + E_dir = side_dir.cross(k_dir); //calculate the new E vector direction + T E_norm = m_E.norm2(); + m_E = E_dir * E_norm; //apply the new direction to the existing E vector + */ + } + + CUDA_CALLABLE cvec3 k() { + return m_k; + } + + CUDA_CALLABLE cvec3 E() { + return m_E; + } + + CUDA_CALLABLE cvec3 evaluate(T x, T y, T z) { + + std::complex k_dot_r = m_k[0] * x + m_k[1] * y + m_k[2] * z; + std::complex e_k_dot_r = std::exp(std::complex(0, 1) * k_dot_r); + + cvec3 result; + result[0] = m_E[0] * e_k_dot_r; + result[1] = m_E[1] * e_k_dot_r; + result[2] = m_E[2] * e_k_dot_r; + return result; + } + + CUDA_CALLABLE T kmag() const { + return std::sqrt(std::real(m_k.get(0) * std::conj(m_k.get(0)) + m_k.get(1) * std::conj(m_k.get(1)) + m_k.get(2) * std::conj(m_k.get(2)))); + } + + /// Return a plane wave with the origin translated by (x, y, z) + CUDA_CALLABLE planewave translate(T x, T y, T z) const { + planewave result; + cvec3 k = m_k; + result.m_k = k; + std::complex k_dot_r = k[0] * (-x) + k[1] * (-y) + k[2] * (-z); + std::complex exp_k_dot_r = std::exp(std::complex(0.0, 1.0) * k_dot_r); + + cvec3 E = m_E; + result.m_E[0] = E[0] * exp_k_dot_r; + result.m_E[1] = E[1] * exp_k_dot_r; + result.m_E[2] = E[2] * exp_k_dot_r; + return result; + } + + ///multiplication operator: scale E0 + CUDA_CALLABLE planewave& operator* (const T& rhs) { + m_E = m_E * rhs; + return *this; + } + + ///return a plane wave with the applied refractive index (scales the k-vector by the input) + CUDA_CALLABLE planewave ri(T n) { + planewave result; + result.m_E = m_E; + result.m_k = m_k * n; + return result; + } + CUDA_CALLABLE planewave refract(vec3 kn) const { + return bend(kn); + } + + /*CUDA_CALLABLE T lambda() const{ + return stim::TAU / k.len(); + } + + CUDA_CALLABLE T kmag() const{ + return k.len(); + } + + CUDA_CALLABLE vec< complex > E(){ + return E0; + } + + CUDA_CALLABLE vec kvec(){ + return k; + } + + /// calculate the value of the field produced by the plane wave given a three-dimensional position + CUDA_CALLABLE vec< complex > pos(T x, T y, T z){ + return pos( stim::vec(x, y, z) ); + } + + CUDA_CALLABLE vec< complex > pos(vec p = vec(0, 0, 0)){ + vec< complex > result; + + T kdp = k.dot(p); + complex x = complex(0, kdp); + complex expx = exp(x); + + result[0] = E0[0] * expx; + result[1] = E0[1] * expx; + result[2] = E0[2] * expx; + + return result; + } + + //scales k based on a transition from material ni to material nt + CUDA_CALLABLE planewave n(T ni, T nt){ + return planewave(k * (nt / ni), E0); + } + + + + /// Calculate the result of a plane wave hitting an interface between two refractive indices + + /// @param P is a plane representing the position and orientation of the surface + /// @param n0 is the refractive index outside of the surface (in the direction of the normal) + /// @param n1 is the refractive index inside the surface (in the direction away from the normal) + /// @param r is the reflected component of the plane wave + /// @param t is the transmitted component of the plane wave + void scatter(stim::plane P, T n0, T n1, planewave &r, planewave &t){ + scatter(P, n1/n0, r, t); + }*/ + + /// Calculate the scattering result when nr = n1/n0 + + /// @param P is a plane representing the position and orientation of the surface + /// @param r is the ratio n1/n0 + /// @param n1 is the refractive index inside the surface (in the direction away from the normal) + /// @param r is the reflected component of the plane wave + /// @param t is the transmitted component of the plane wave + + int scatter(vec3 plane_normal, vec3 plane_position, std::complex nr, planewave& r, planewave& t) { + + if (m_k[0].imag() != 0.0 || m_k[1].imag() != 0.0 || m_k[2].imag() != 0) { + std::cout << "ERROR: cannot scatter a plane wave with an imaginary k-vector." << std::endl; + } + + vec3 ki(m_k[0].real(), m_k[1].real(), m_k[2].real()); //force the current k vector to be real + vec3 kr; + cvec3 kt, Ei, Er, Et; + + plane_normal = plane_normal.direction(); + vec3 k_dir = ki.direction(); //calculate the direction of the incident plane wave + + //int facing = plane_face(k_dir, plane_normal); //determine which direction the plane wave is coming in + if (k_dir.dot(plane_normal) > 0) { //if the wave hits the back of the plane, invert the plane and nr + std::cout << "ERROR: k-vector intersects the wrong side of the boundary." << std::endl; + return -1; //the plane wave is impacting the wrong side of the surface + } + + //use Snell's Law to calculate the transmitted angle + T cos_theta_i = k_dir.dot(-plane_normal); //compute the cosine of theta_i + T sin_theta_i = std::sqrt(1 - cos_theta_i * cos_theta_i); + T theta_i = acos(cos_theta_i); //compute theta_i + + //handle the degenerate case where theta_i is 0 (the plane wave hits head-on) + if (theta_i == 0) { + std::complex rp = (1.0 - nr) / (1.0 + nr); //compute the Fresnel coefficients + std::complex tp = 2.0 / (1.0 + nr); + + kr = -ki; //the reflection vector is the inverse of the incident vector + kt[0] = ki[0] * nr; + kt[1] = ki[1] * nr; + kt[2] = ki[2] * nr; + + Er = m_E * rp; //compute the E vectors based on the Fresnel coefficients + Et = m_E * tp; + + //calculate the phase offset based on the plane positions + T phase_r = plane_position.dot(ki - kr); + std::complex phase_t = + plane_position[0] * (ki[0] - kt[0]) + + plane_position[1] * (ki[1] - kt[1]) + + plane_position[2] * (ki[2] - kt[2]); + } + else { + T cos_theta_r = cos_theta_i; + T sin_theta_r = sin_theta_i; + T theta_r = theta_i; + + std::complex sin_theta_t = (1.0/nr) * sin(theta_i); //compute the sine of theta_t using Snell's law + std::complex cos_theta_t = std::sqrt(1.0 - sin_theta_t * sin_theta_t); + std::complex theta_t = asin(sin_theta_t); //compute the cosine of theta_t + + //Define the basis vectors for the calculation (plane of incidence) + vec3 z_hat = -plane_normal; + vec3 plane_perpendicular = plane_normal * k_dir.dot(plane_normal); + vec3 y_hat = (k_dir - plane_perpendicular).direction(); + vec3 x_hat = y_hat.cross(z_hat); + + //calculate the k-vector magnitudes + T ki_mag = ki.norm2(); + T kr_mag = ki_mag; + std::complex kt_mag = ki_mag * nr; + + //calculate the k vector directions + vec3 ki_dir = y_hat * sin_theta_i + z_hat * cos_theta_i; + vec3 kr_dir = y_hat * sin_theta_r - z_hat * cos_theta_r; + cvec3 kt_dir; + kt_dir[0] = y_hat[0] * sin_theta_t + z_hat[0] * cos_theta_t; + kt_dir[1] = y_hat[1] * sin_theta_t + z_hat[1] * cos_theta_t; + kt_dir[2] = y_hat[2] * sin_theta_t + z_hat[2] * cos_theta_t; + + //calculate the k vectors + ki = ki_dir * ki_mag; + kr = kr_dir * kr_mag; + kt = kt_dir * kt_mag; + + //calculate the Fresnel coefficients + std::complex rs = std::sin(theta_t - theta_i) / std::sin(theta_t + theta_i); + std::complex rp = std::tan(theta_t - theta_i) / std::tan(theta_t + theta_i); + std::complex ts = (2.0 * (sin_theta_t * cos_theta_i)) / std::sin(theta_t + theta_i); + std::complex tp = ((2.0 * sin_theta_t * cos_theta_i) / (std::sin(theta_t + theta_i) * std::cos(theta_t - theta_i))); + + //calculate the p component directions for each E vector + vec3 Eip_dir = y_hat * cos_theta_i - z_hat * sin_theta_i; + vec3 Erp_dir = y_hat * cos_theta_r + z_hat * sin_theta_r; + cvec3 Etp_dir; + Etp_dir[0] = y_hat[0] * cos_theta_t - z_hat[0] * sin_theta_t; + Etp_dir[1] = y_hat[1] * cos_theta_t - z_hat[1] * sin_theta_t; + Etp_dir[2] = y_hat[2] * cos_theta_t - z_hat[2] * sin_theta_t; + + //calculate the s and t components of each E vector + std::complex Ei_s = m_E.dot(x_hat); + std::complex Ei_p = m_E.dot(Eip_dir); + std::complex Er_s = rs * Ei_s; + std::complex Er_p = rp * Ei_p; + std::complex Et_s = ts * Ei_s; + std::complex Et_p = tp * Ei_p; + + //calculate the E vector for each plane wave + Er[0] = Erp_dir[0] * Er_p + x_hat[0] * Er_s; + Er[1] = Erp_dir[1] * Er_p + x_hat[1] * Er_s; + Er[2] = Erp_dir[2] * Er_p + x_hat[2] * Er_s; + + Et[0] = Etp_dir[0] * Et_p + x_hat[0] * Et_s; + Et[1] = Etp_dir[1] * Et_p + x_hat[1] * Et_s; + Et[2] = Etp_dir[2] * Et_p + x_hat[2] * Et_s; + } + + + //calculate the phase offset based on the plane positions + T phase_r = plane_position.dot(ki - kr); + std::complex phase_t = + plane_position[0] * (ki[0] - kt[0]) + + plane_position[1] * (ki[1] - kt[1]) + + plane_position[2] * (ki[2] - kt[2]); + + //apply the phase offset + Er = Er * std::exp(std::complex(0, 1) * phase_r); + Et = Et * std::exp(std::complex(0, 1) * phase_t); + + //generate the reflected and transmitted waves + r = planewave(kr[0], kr[1], kr[2], Er[0], Er[1], Er[2]); + t = planewave(kt[0], kt[1], kt[2], Et[0], Et[1], Et[2]); + + return 0; + } + + std::string str() + { + std::stringstream ss; + ss << "k: " << m_k << std::endl; + ss << "E: " << m_E << std::endl; + return ss.str(); + } +}; //end planewave class +} //end namespace optics +} //end namespace tira + +template +std::ostream& operator<<(std::ostream& os, tira::optics::planewave p) +{ + os< + +#include "../math/vec3.h" +#include "../optics/scalarwave.h" +#include "../math/bessel.h" +#include "../math/legendre.h" +#include "../cuda/cudatools/devices.h" +#include "../cuda/cudatools/timer.h" +#include "../optics/scalarfield.h" +#include +#include +#include +#include + + + +namespace stim{ + +/// Function returns the value of the scalar field produced by a beam with the specified parameters + +template +std::vector< stim::vec3 > generate_focusing_vectors(size_t N, stim::vec3 d, T NA, T NA_in = 0){ + + std::vector< stim::vec3 > dirs(N); //allocate an array to store the focusing vectors + + ///compute the rotation operator to transform (0, 0, 1) to k + T cos_angle = d.dot(vec3(0, 0, 1)); + stim::matrix_sq rotation; + + //if the cosine of the angle is -1, the rotation is just a flip across the z axis + if(cos_angle == -1){ + rotation(2, 2) = -1; + } + else if(cos_angle != 1.0) + { + vec3 r_axis = vec3(0, 0, 1).cross(d).norm(); //compute the axis of rotation + T angle = acos(cos_angle); //compute the angle of rotation + quaternion quat; //create a quaternion describing the rotation + quat.CreateRotation(angle, r_axis); + rotation = quat.toMatrix3(); //compute the rotation matrix + } + + //find the phi values associated with the cassegrain ring + T PHI[2]; + PHI[0] = (T)asin(NA); + PHI[1] = (T)asin(NA_in); + + //calculate the z-axis cylinder coordinates associated with these angles + T Z[2]; + Z[0] = cos(PHI[0]); + Z[1] = cos(PHI[1]); + T range = Z[0] - Z[1]; + + //draw a distribution of random phi, z values + T z, phi, theta; + //T kmag = stim::TAU / lambda; + for(int i=0; i spherical(1, theta, phi); //convert from spherical to cartesian coordinates + vec3 cart = spherical.sph2cart(); + dirs[i] = rotation * cart; //create a sample vector + } + return dirs; +} + + +/// Calculate the [0 Nl] terms for the aperture integral based on the give numerical aperture and center obscuration (optional) +/// @param C is a pointer to Nl + 1 values where the terms will be stored +template +CUDA_CALLABLE void cpu_aperture_integral(T* C, int Nl, T NA, T NA_in = 0){ + + size_t table_bytes = (Nl + 1) * sizeof(T); //calculate the number of bytes required to store the terms + T cos_alpha_1 = cos(asin(NA_in)); //calculate the cosine of the angle subtended by the central obscuration + T cos_alpha_2 = cos(asin(NA)); //calculate the cosine of the angle subtended by the aperture + + // the aperture integral is computed using four individual Legendre polynomials, each a function of the angles subtended + // by the objective and central obscuration + T* Pln_a1 = (T*) malloc(table_bytes); + stim::legendre(Nl-1, cos_alpha_1, &Pln_a1[1]); + Pln_a1[0] = 1; + + T* Pln_a2 = (T*) malloc(table_bytes); + stim::legendre(Nl-1, cos_alpha_2, &Pln_a2[1]); + Pln_a2[0] = 1; + + T* Plp_a1 = (T*) malloc(table_bytes+sizeof(T)); + stim::legendre(Nl+1, cos_alpha_1, Plp_a1); + + T* Plp_a2 = (T*) malloc(table_bytes+sizeof(T)); + stim::legendre(Nl+1, cos_alpha_2, Plp_a2); + + for(size_t l = 0; l <= Nl; l++){ + C[l] = Plp_a1[l+1] - Plp_a2[l+1] - Pln_a1[l] + Pln_a2[l]; + } + + free(Pln_a1); + free(Pln_a2); + free(Plp_a1); + free(Plp_a2); +} + +/// performs linear interpolation into a look-up table +template +CUDA_CALLABLE void lut_lookup(T* lut_values, T* lut, T val, size_t N, T min_val, T delta, size_t n_vals){ + T idx = ((val - min_val) / delta); + size_t i = (size_t) idx; + T a1 = idx - i; + T a0 = 1 - a1; + size_t n0 = i * n_vals; + size_t n1 = (i+1) * n_vals; + for(size_t n = 0; n < n_vals; n++){ + lut_values[n] = lut[n0 + n] * a0 + lut[n1 + n] * a1; + } +} + +template +CUDA_CALLABLE stim::complex clerp(stim::complex v0, stim::complex v1, T t) { + return stim::complex( fmaf(t, v1.r, fmaf(-t, v0.r, v0.r)), fmaf(t, v1.i, fmaf(-t, v0.i, v0.i)) ); +} + +template +CUDA_CALLABLE T lerp(T v0, T v1, T t) { + return fmaf(t, v1, fmaf(-t, v0, v0)); +} + +#ifdef CUDA_FOUND +template +__global__ void cuda_scalar_psf(stim::complex* E, size_t N, T* r, T* phi, T A, size_t Nl, + T* C, + T* lut_j, size_t Nj, T min_r, T dr){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the array + if(i >= N) return; //exit if this thread is outside the array + + T cos_phi = cos(phi[i]); //calculate the thread value for cos(phi) + stim::complex Ei = 0; //initialize the value of the field to zero + size_t NC = Nl + 1; //calculate the number of coefficients to be used + + T fij = (r[i] - min_r)/dr; //FP index into the spherical bessel LUT + size_t ij = (size_t) fij; //convert to an integral index + T a = fij - ij; //calculate the fractional portion of the index + size_t n0j = ij * (NC); //start of the first entry in the LUT + size_t n1j = (ij+1) * (NC); //start of the second entry in the LUT + + T jl; //declare register to store the spherical bessel function + T Pl_2, Pl_1; //declare registers to store the previous two Legendre polynomials + T Pl = 1; //initialize the current value for the Legendre polynomial + stim::complex im(0, 1); //declare i (imaginary 1) + stim::complex i_pow(1, 0); //i_pow stores the current value of i^l so it doesn't have to be re-computed every iteration + for(int l = 0; l <= Nl; l++){ //for each order + jl = lerp( lut_j[n0j + l], lut_j[n1j + l], a ); //read jl from the LUT and interpolate the result + Ei += i_pow * jl * Pl * C[l]; //calculate the value for the field and sum + i_pow *= im; //multiply i^l * i for the next iteration + Pl_2 = Pl_1; //shift Pl_1 -> Pl_2 and Pl -> Pl_1 + Pl_1 = Pl; + if(l == 0){ //computing Pl is done recursively, where the recursive relation + Pl = cos_phi; // requires the first two orders. This defines the second. + } + else{ //if this is not the first iteration, use the recursive relation to calculate Pl + Pl = ( (2 * (l+1) - 1) * cos_phi * Pl_1 - (l) * Pl_2 ) / (l+1); + } + + } + E[i] = Ei * A * 2 * CUDART_PI_F; //scale the integral by the amplitude +} + +template +void gpu_scalar_psf_local(stim::complex* E, size_t N, T* r, T* phi, T lambda, T A, T NA, T NA_in, int Nl, T r_spacing){ + + //Find the minimum and maximum values of r + cublasStatus_t stat; + cublasHandle_t handle; + + stat = cublasCreate(&handle); //create a cuBLAS handle + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS initialization failed\n"); + exit(1); + } + + int i_min, i_max; + stat = cublasIsamin(handle, (int)N, r, 1, &i_min); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate minimum r value.\n"); + exit(1); + } + stat = cublasIsamax(handle, (int)N, r, 1, &i_max); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate maximum r value.\n"); + exit(1); + } + cublasDestroy(handle); + + i_min--; //cuBLAS uses 1-based indexing for Fortran compatibility + i_max--; + T r_min, r_max; //allocate space to store the minimum and maximum values + HANDLE_ERROR( cudaMemcpy(&r_min, r + i_min, sizeof(T), cudaMemcpyDeviceToHost) ); //copy the min and max values from the device to the CPU + HANDLE_ERROR( cudaMemcpy(&r_max, r + i_max, sizeof(T), cudaMemcpyDeviceToHost) ); + + T k = (T)stim::TAU / lambda; //calculate the wavenumber from lambda + size_t C_bytes = (Nl + 1) * sizeof(T); + T* C = (T*) malloc( C_bytes ); //allocate space for the aperture integral terms + cpu_aperture_integral(C, Nl, NA, NA_in); //calculate the aperture integral terms + + size_t Nlut_j = (size_t)((r_max - r_min) / r_spacing + 1); //number of values in the look-up table based on the user-specified spacing along r + + + size_t lutj_bytes = sizeof(T) * (Nl+1) * Nlut_j; + T* j_lut = (T*) malloc(lutj_bytes); //pointer to the look-up table + T dr = (r_max - r_min) / (Nlut_j-1); //distance between values in the LUT + T jl; + unsigned l; + for(size_t ri = 0; ri < Nlut_j; ri++){ //for each value in the LUT + for(l = 0; l <= (unsigned)Nl; l++){ //for each order + jl = boost::math::sph_bessel(l, k*(r_min + ri * dr)); //use boost to calculate the spherical bessel function + j_lut[ri * (Nl + 1) + l] = jl; //store the bessel function result + } + } + + //stim::cpu2image(j_lut, "j_lut.bmp", Nl+1, Nlut_j, stim::cmBrewer); + //Allocate device memory and copy everything to the GPU + + T* gpu_C; + HANDLE_ERROR( cudaMalloc(&gpu_C, C_bytes) ); + HANDLE_ERROR( cudaMemcpy(gpu_C, C, C_bytes, cudaMemcpyHostToDevice) ); + T* gpu_j_lut; + HANDLE_ERROR( cudaMalloc(&gpu_j_lut, lutj_bytes) ); + HANDLE_ERROR( cudaMemcpy(gpu_j_lut, j_lut, lutj_bytes, cudaMemcpyHostToDevice) ); + + int threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 blocks( (unsigned)(N / threads + 1)); //calculate the optimal number of blocks + + cuda_scalar_psf<<< blocks, threads >>>(E, N, r, phi, A, Nl, gpu_C, gpu_j_lut, Nlut_j, r_min, dr); + + //free the LUT and condenser tables + HANDLE_ERROR( cudaFree(gpu_C) ); + HANDLE_ERROR( cudaFree(gpu_j_lut) ); +} +#endif + +/// Calculate the analytical solution to a scalar point spread function given a set of spherical coordinates about the PSF (beam propagation along phi = theta = 0) +template +void cpu_scalar_psf_local(stim::complex* F, size_t N, T* r, T* phi, T lambda, T A, T NA, T NA_in, int Nl){ + T k = (T)stim::TAU / lambda; + size_t C_bytes = (Nl + 1) * sizeof(T); + T* C = (T*) malloc( C_bytes ); //allocate space for the aperture integral terms + cpu_aperture_integral(C, Nl, NA, NA_in); //calculate the aperture integral terms + memset(F, 0, N * sizeof(stim::complex)); + T jl, Pl, kr, cos_phi; + + double vm; + double* jv = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* yv = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* djv= (double*) malloc( (Nl + 1) * sizeof(double) ); + double* dyv= (double*) malloc( (Nl + 1) * sizeof(double) ); + + T* Pl_cos_phi = (T*) malloc((Nl + 1) * sizeof(T)); + + for(size_t n = 0; n < N; n++){ //for each point in the field + kr = k * r[n]; //calculate kr (the optical distance between the focal point and p) + cos_phi = std::cos(phi[n]); //calculate the cosine of phi + stim::bessjyv_sph(Nl, kr, vm, jv, yv, djv, dyv); //compute the list of spherical bessel functions from [0 Nl] + stim::legendre(Nl, cos_phi, Pl_cos_phi); //calculate the [0 Nl] legendre polynomials for this point + + for(int l = 0; l <= Nl; l++){ + jl = (T)jv[l]; + Pl = Pl_cos_phi[l]; + F[n] += pow(complex(0, 1), l) * jl * Pl * C[l]; + } + F[n] *= A * stim::TAU; + } + + free(C); + free(Pl_cos_phi); +} + +/// Converts a set of cartesian points into spherical coordinates surrounding a point spread function (PSF) +/// @param r is the output distance from the PSF +/// @param phi is the non-symmetric direction about the PSF +/// @param x (x, y, z) are the cartesian coordinates in world space +/// @f is the focal point of the PSF in cartesian coordinates +/// @d is the propagation direction of the PSF in cartesian coordinates +template +__global__ void cuda_cart2psf(T* r, T* phi, size_t N, T* x, T* y, T* z, stim::vec3 f, stim::quaternion q){ + + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the array + if(i >= N) return; //exit if this thread is outside the array + + stim::vec3 p; //declare a 3D point + + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + + p = p - f; //shift the point to the center of the PSF (focal point) + p = q.toMatrix3() * p; //rotate the point to align with the propagation direction + + stim::vec3 ps = p.cart2sph(); //convert from cartesian to spherical coordinates + r[i] = ps[0]; //store r + phi[i] = ps[2]; //phi = [0 pi] +} + +#ifdef CUDA_FOUND +/// Calculate the analytical solution to a point spread function given a set of points in cartesian coordinates +template +void gpu_scalar_psf_cart(stim::complex* E, size_t N, T* x, T* y, T* z, T lambda, T A, stim::vec3 f, stim::vec3 d, T NA, T NA_in, int Nl, T r_spacing = 1){ + + T* gpu_r; //allocate space for the coordinates in r + HANDLE_ERROR( cudaMalloc(&gpu_r, sizeof(T) * N) ); + T* gpu_phi; + HANDLE_ERROR( cudaMalloc(&gpu_phi, sizeof(T) * N) ); + //stim::complex* gpu_E; + //HANDLE_ERROR( cudaMalloc(&gpu_E, sizeof(stim::complex) * N) ); + + stim::quaternion q; //create a quaternion + q.CreateRotation(d, stim::vec3(0, 0, 1)); //create a mapping from the propagation direction to the PSF space + stim::matrix_sq rot = q.toMatrix3(); + int threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 blocks( (unsigned)(N / threads + 1)); //calculate the optimal number of blocks + cuda_cart2psf <<< blocks, threads >>> (gpu_r, gpu_phi, N, x, y, z, f, q); //call the CUDA kernel to move the cartesian coordinates to PSF space + + gpu_scalar_psf_local(E, N, gpu_r, gpu_phi, lambda, A, NA, NA_in, Nl, r_spacing); + + HANDLE_ERROR( cudaFree(gpu_r) ); + HANDLE_ERROR( cudaFree(gpu_phi) ); + +} +#endif + +template +void cpu_scalar_psf_cart(stim::complex* E, size_t N, T* x, T* y, T* z, T lambda, T A, stim::vec3 f, stim::vec3 d, T NA, T NA_in, int Nl, T r_spacing = 1){ + +// If CUDA is available, copy the cartesian points to the GPU and evaluate them in a kernel +#ifdef CUDA_FOUND + + T* gpu_x = NULL; + if(x != NULL){ + HANDLE_ERROR( cudaMalloc(&gpu_x, sizeof(T) * N) ); + HANDLE_ERROR( cudaMemcpy(gpu_x, x, sizeof(T) * N, cudaMemcpyHostToDevice) ); + } + T* gpu_y = NULL; + if(y != NULL){ + HANDLE_ERROR( cudaMalloc(&gpu_y, sizeof(T) * N) ); + HANDLE_ERROR( cudaMemcpy(gpu_y, y, sizeof(T) * N, cudaMemcpyHostToDevice) ); + } + T* gpu_z = NULL; + if(z != NULL){ + HANDLE_ERROR( cudaMalloc(&gpu_z, sizeof(T) * N) ); + HANDLE_ERROR( cudaMemcpy(gpu_z, z, sizeof(T) * N, cudaMemcpyHostToDevice) ); + } + + stim::complex* gpu_E; + HANDLE_ERROR( cudaMalloc(&gpu_E, sizeof(stim::complex) * N) ); + HANDLE_ERROR( cudaMemcpy(gpu_E, E, sizeof(stim::complex) * N, cudaMemcpyHostToDevice) ); + gpu_scalar_psf_cart(gpu_E, N, gpu_x, gpu_y, gpu_z, lambda, A, f, d, NA, NA_in, Nl, r_spacing); + HANDLE_ERROR( cudaMemcpy(E, gpu_E, sizeof(stim::complex) * N, cudaMemcpyDeviceToHost) ); + + HANDLE_ERROR( cudaFree(gpu_x) ); + HANDLE_ERROR( cudaFree(gpu_y) ); + HANDLE_ERROR( cudaFree(gpu_z) ); + HANDLE_ERROR( cudaFree(gpu_E) ); + +#else + T* r = (T*) malloc(N * sizeof(T)); //allocate space for p in spherical coordinates + T* phi = (T*) malloc(N * sizeof(T)); // only r and phi are necessary (the scalar PSF is symmetric about theta) + + stim::quaternion q; + q.CreateRotation(d, stim::vec3(0, 0, 1)); + stim::matrix R = q.toMatrix3(); + stim::vec3 p, ps, ds; + for(size_t i = 0; i < N; i++){ + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + + p = p - f; + + p = R * p; //rotate the cartesian point + + ps = p.cart2sph(); //convert from cartesian to spherical coordinates + r[i] = ps[0]; //store r + phi[i] = ps[2]; //phi = [0 pi] + } + + cpu_scalar_psf_local(E, N, r, phi, lambda, A, NA, NA_in, Nl); //call the spherical coordinate CPU function + + free(r); + free(phi); +#endif +} + +/// Class stim::beam represents a beam of light focused at a point and composed of several plane waves +template +class scalarbeam +{ +public: + //enum beam_type {Uniform, Bartlett, Hamming, Hanning}; + +private: + + T NA[2]; //numerical aperature of the focusing optics + vec3 f; //focal point + vec3 d; //propagation direction + T A; //beam amplitude + T lambda; //beam wavelength +public: + + ///constructor: build a default beam (NA=1.0) + scalarbeam(T wavelength = 1, T amplitude = 1, vec3 focal_point = vec3(0, 0, 0), vec3 direction = vec3(0, 0, 1), T numerical_aperture = 1, T center_obsc = 0){ + lambda = wavelength; + A = amplitude; + f = focal_point; + d = direction.norm(); //make sure that the direction vector is normalized (makes calculations more efficient later on) + NA[0] = center_obsc; + NA[1] = numerical_aperture; + } + + ///Numerical Aperature functions + void setNA(T na) + { + NA[0] = (T)0; + NA[1] = na; + } + void setNA(T na_in, T na_out) + { + NA[0] = na_in; + NA[1] = na_out; + } + + //Monte-Carlo decomposition into plane waves + std::vector< scalarwave > mc(size_t N = 100000) const{ + + T kmag = (T)stim::TAU / lambda; //calculate the wavenumber + stim::vec3 kpw; //declare the new k-vector based on the focused plane wave direction + stim::complex apw; //allocate space for the amplitude at the focal point + + //deal with the degenerative case where the outer NA is 0, in which case the beam will be specified as a single plane wave + if (NA[1] == 0) { + std::vector< scalarwave > samples(1); //create a vector containing 1 sample + kpw = d * kmag; //calculate the k-vector for the plane wave (beam direction scaled by wavenumber) + apw = A * exp(stim::complex(0, kpw.dot(-f))); //calculate the amplitude of the plane wave + samples[0] = scalarwave(kpw, apw); //create a plane wave based on the direction + return samples; + } + + //otherwise, evaluate the system using N monte-carlo samples + std::vector< stim::vec3 > dirs = generate_focusing_vectors(N, d, NA[0], NA[1]); //generate a random set of N vectors forming a focus + std::vector< scalarwave > samples(N); //create a vector of plane waves + T a = (T)(stim::TAU * ( (1 - cos(asin(NA[0]))) - (1 - cos(asin(NA[1])))) / (double)N); //constant value weights plane waves based on the aperture and number of samples (N) + for(size_t i=0; i(0, kpw.dot(-f))); //calculate the amplitude for the new plane wave + samples[i] = scalarwave(kpw, apw); //create a plane wave based on the direction + } + return samples; + } + + void eval(stim::scalarfield& E, T* X, T* Y, T* Z, int order = 500){ + cpu_scalar_psf_cart(E.ptr(), E.size(), X, Y, Z, lambda, A, f, d, NA[1], NA[0], order, E.spacing()); + } + + /// Evaluate the beam to a scalar field using Debye focusing + void eval(stim::scalarfield& E, int order = 500){ + E.meshgrid(); //calculate a meshgrid if one isn't created + + if(E.gpu()) + gpu_scalar_psf_cart(E.ptr(), E.size(), E.x(), E.y(), E.z(), lambda, A, f, d, NA[1], NA[0], order, E.spacing()); + else + cpu_scalar_psf_cart(E.ptr(), E.size(), E.x(), E.y(), E.z(), lambda, A, f, d, NA[1], NA[0], order, E.spacing()); + + } + + /// Calculate the field at a given point + /// @param x is the x-coordinate of the field point + /// @O is the approximation accuracy + stim::complex field(T x, T y, T z, size_t O){ + std::vector< scalarwave > W = mc(O); + T result = 0; //initialize the result to zero (0) + for(size_t i = 0; i < O; i++){ //for each plane wave + result += W[i].pos(x, y, z); + } + return result; + } + + std::string str() + { + std::stringstream ss; + ss<<"Beam:"< + __global__ void cuda_abs(T* img, stim::complex* field, size_t N){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + img[i] = field[i].abs(); + } + + template + __global__ void cuda_real(T* img, stim::complex* field, size_t N){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + img[i] = field[i].real(); + } + + template + __global__ void cuda_imag(T* img, stim::complex* field, size_t N){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + img[i] = field[i].imag(); + } + + template + __global__ void cuda_intensity(T* img, stim::complex* field, size_t N){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + img[i] = pow(field[i].abs(), 2); + } + + template + __global__ void cuda_sum_intensity(T* img, stim::complex* field, size_t N){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + img[i] += pow(field[i].abs(), 2); + } + + /// Perform a k-space transform of a scalar field (FFT). The given field has a width of x and the calculated momentum space has a + /// width of kx (in radians). + /// @param K is a pointer to the output array of all plane waves in the field + /// @param kx is the width of the frame in momentum space + /// @param ky is the height of the frame in momentum space + /// @param E is the field to be transformed + /// @param x is the width of the field in the spatial domain + /// @param y is the height of the field in the spatial domain + /// @param nx is the number of pixels representing the field in the x (and kx) direction + /// @param ny is the number of pixels representing the field in the y (and ky) direction + template + void cpu_scalar_to_kspace(stim::complex* K, T& kx, T& ky, stim::complex* E, T x, T y, size_t nx, size_t ny){ + + kx = stim::TAU * nx / x; //calculate the width of the momentum space + ky = stim::TAU * ny / y; + + stim::complex* dev_FFT; + HANDLE_ERROR( cudaMalloc(&dev_FFT, sizeof(stim::complex) * nx * ny) ); //allocate space on the CUDA device for the output array + + stim::complex* dev_E; + HANDLE_ERROR( cudaMalloc(&dev_E, sizeof(stim::complex) * nx * ny) ); //allocate space for the field + HANDLE_ERROR( cudaMemcpy(dev_E, E, sizeof(stim::complex) * nx * ny, cudaMemcpyHostToDevice) ); //copy the field to GPU memory + + cufftResult result; + cufftHandle plan; + result = cufftPlan2d(&plan, nx, ny, CUFFT_C2C); + if(result != CUFFT_SUCCESS){ + std::cout<<"Error creating cuFFT plan."<* fft = (stim::complex*) malloc(sizeof(stim::complex) * nx * ny); + HANDLE_ERROR( cudaMemcpy(fft, dev_FFT, sizeof(stim::complex) * nx * ny, cudaMemcpyDeviceToHost) ); + + stim::cpu_fftshift(K, fft, nx, ny); + + HANDLE_ERROR( cudaFree(dev_FFT) ); //free GPU memory + HANDLE_ERROR( cudaFree(dev_E) ); + free(fft); //free CPU memory + } + + template + void cpu_scalar_from_kspace(stim::complex* E, T& x, T& y, stim::complex* K, T kx, T ky, size_t nx, size_t ny){ + + x = stim::TAU * nx / kx; //calculate the width of the momentum space + y = stim::TAU * ny / ky; + + stim::complex* fft = (stim::complex*) malloc(sizeof(stim::complex) * nx * ny); + stim::cpu_ifftshift(fft, K, nx, ny); + //memcpy(fft, K, sizeof(stim::complex) * nx * ny); + + stim::complex* dev_FFT; + HANDLE_ERROR( cudaMalloc(&dev_FFT, sizeof(stim::complex) * nx * ny) ); //allocate space on the CUDA device for the output array + HANDLE_ERROR( cudaMemcpy(dev_FFT, fft, sizeof(stim::complex) * nx * ny, cudaMemcpyHostToDevice) ); //copy the field to GPU memory + + stim::complex* dev_E; + HANDLE_ERROR( cudaMalloc(&dev_E, sizeof(stim::complex) * nx * ny) ); //allocate space for the field + + cufftResult result; + cufftHandle plan; + result = cufftPlan2d(&plan, nx, ny, CUFFT_C2C); + if(result != CUFFT_SUCCESS){ + std::cout<<"Error creating cuFFT plan."<) * nx * ny, cudaMemcpyDeviceToHost) ); + + HANDLE_ERROR( cudaFree(dev_FFT) ); //free GPU memory + HANDLE_ERROR( cudaFree(dev_E) ); + free(fft); //free CPU memory + + } + + + + /// Propagate a field slice along its orthogonal direction by a given distance z + /// @param Enew is the resulting propagated field + /// @param E is the field to be propagated + /// @param sx is the size of the field in the lateral x direction + /// @param sy is the size of the field in the lateral y direction + /// @param z is the distance to be propagated + /// @param k is the wavenumber 2*pi/lambda + /// @param nx is the number of samples in the field along the lateral x direction + /// @param ny is the number of samples in the field along the lateral y direction + template + void cpu_scalar_propagate(stim::complex* Enew, stim::complex* E, T sx, T sy, T z, T k, size_t nx, size_t ny){ + + stim::complex* K = (stim::complex*) malloc( sizeof(stim::complex) * nx * ny ); + + T Kx, Ky; //width and height in k space + cpu_scalar_to_kspace(K, Kx, Ky, E ,sx, sy, nx, ny); + + //T* mag = (T*) malloc( sizeof(T) * nx * ny ); + //stim::abs(mag, K, nx * ny); + //stim::cpu2image(mag, "kspace_pre_shift.bmp", nx, ny, stim::cmBrewer); + + size_t kxi, kyi; + size_t i; + T kx, kx_sq, ky, ky_sq, k_sq; + T kz; + stim::complex shift; + T min_kx = -Kx / 2; + T dkx = Kx / (nx); + + T min_ky = -Ky / 2; + T dky = Ky / (ny); + + for(kyi = 0; kyi < ny; kyi++){ //for each plane wave in the ky direction + for(kxi = 0; kxi < nx; kxi++){ //for each plane wave in the ky direction + i = kyi * nx + kxi; + + kx = min_kx + kxi * dkx; //calculate the position of the current plane wave + ky = min_ky + kyi * dky; + + kx_sq = kx * kx; + ky_sq = ky * ky; + k_sq = k*k; + + if(kx_sq + ky_sq < k_sq){ + kz = sqrt(k_sq - kx_sq - ky_sq); //estimate kz using the Fresnel approximation + shift = exp(stim::complex(0, kz * z)); + //std::cout << shift << std::endl; + K[i] *= shift; + K[i] /= (nx*ny); //normalize the DFT + } + else{ + K[i] /= (nx*ny); + } + } + } + + //stim::abs(mag, K, nx * ny); + //stim::cpu2image(mag, "kspace_post_shift.bmp", nx, ny, stim::cmBrewer); + + cpu_scalar_from_kspace(Enew, sx, sy, K, Kx, Ky, nx, ny); + free(K); + } + + template + void gpu_scalar_propagate(stim::complex* Enew, stim::complex* E, T sx, T sy, T z, T k, size_t nx, size_t ny){ + + size_t field_bytes = sizeof(stim::complex) * nx * ny; + stim::complex* host_E = (stim::complex*) malloc( field_bytes); + HANDLE_ERROR( cudaMemcpy(host_E, E, field_bytes, cudaMemcpyDeviceToHost) ); + + stim::complex* host_Enew = (stim::complex*) malloc(field_bytes); + + cpu_scalar_propagate(host_Enew, host_E, sx, sy, z, k, nx, ny); + + HANDLE_ERROR( cudaMemcpy(Enew, host_Enew, field_bytes, cudaMemcpyHostToDevice) ); + free(host_E); + free(host_Enew); + } + + /// Apply a lowpass filter to a field slice + /// @param Enew is the resulting propogated field + /// @param E is the field to be propogated + /// @param sx is the size of the field in the lateral x direction + /// @param sy is the size of the field in the lateral y direction + /// @param highest is the highest spatial frequency that can pass through the filter + /// @param nx is the number of samples in the field along the lateral x direction + /// @param ny is the number of samples in the field along the lateral y direction + template + void cpu_scalar_lowpass(stim::complex* Enew, stim::complex* E, T sx, T sy, T highest, size_t nx, size_t ny){ + + stim::complex* K = (stim::complex*) malloc( sizeof(stim::complex) * nx * ny ); + + T Kx, Ky; //width and height in k space + cpu_scalar_to_kspace(K, Kx, Ky, E ,sx, sy, nx, ny); + + //T* mag = (T*) malloc( sizeof(T) * nx * ny ); + //stim::abs(mag, K, nx * ny); + //stim::cpu2image(mag, "kspace_pre_lowpass.bmp", nx, ny, stim::cmBrewer); + + size_t kxi, kyi; + size_t i; + T kx, kx_sq, ky, ky_sq, k_sq; + T kz; + stim::complex shift; + T min_kx = -Kx / 2; + T dkx = Kx / (nx); + + T min_ky = -Ky / 2; + T dky = Ky / (ny); + + T highest_sq = highest * highest; + + for(kyi = 0; kyi < ny; kyi++){ //for each plane wave in the ky direction + for(kxi = 0; kxi < nx; kxi++){ //for each plane wave in the ky direction + i = kyi * nx + kxi; + + kx = min_kx + kxi * dkx; //calculate the position of the current plane wave + ky = min_ky + kyi * dky; + + kx_sq = kx * kx; + ky_sq = ky * ky; + + if(kx_sq + ky_sq > highest_sq){ + K[i] = 0; + } + else + K[i] /= nx * ny; //normalize the DFT + } + } + + //stim::abs(mag, K, nx * ny); + //stim::cpu2image(mag, "kspace_post_lowpass.bmp", nx, ny, stim::cmBrewer); + + cpu_scalar_from_kspace(Enew, sx, sy, K, Kx, Ky, nx, ny); + free(K); + } + + enum locationType {CPUmem, GPUmem}; + + /// Class represents a scalar optical field. + + /// In general, this class is designed to operate between the CPU and GPU. So, make sure all functions have an option to create the output on either. + /// The field is stored *either* on the GPU or host memory, but not both. This enforces that there can't be different copies of the same field. + /// This class is designed to be included in all of the other scalar optics classes, allowing them to render output data so make sure to keep it general and compatible. + +template +class scalarfield : public rect{ + +protected: + stim::complex* E; + size_t R[2]; + locationType loc; + using rect::X; + using rect::Y; + + T* p[3]; //scalar position for each point in E + + /// Convert the field to a k-space representation (do an FFT) + void to_kspace(T& kx, T& ky){ + cpu_scalar_to_kspace(E, kx, ky, E, X.len(), Y.len(), R[0], R[1]); + } + + void from_kspace(T& kx, T& ky){ + kx = stim::TAU * R[0] / X.len(); //calculate the width of the momentum space + ky = stim::TAU * R[1] / Y.len(); + T x, y; + cpu_scalar_from_kspace(E, x, y, E, kx, ky, R[0], R[1]); + } + +public: + + /// Returns the number of values in the field + CUDA_CALLABLE size_t size(){ + return R[0] * R[1]; + } + + CUDA_CALLABLE size_t grid_bytes(){ + return sizeof(stim::complex) * R[0] * R[1]; + } + + scalarfield(size_t X, size_t Y, T size = 1, T z_pos = 0) : rect::rect(size, z_pos){ + R[0] = X; //set the field resolution + R[1] = Y; + + E = (stim::complex*) malloc(grid_bytes()); //allocate in CPU memory + memset(E, 0, grid_bytes()); + loc = CPUmem; + + p[0] = p[1] = p[2] = NULL; //set the position vector to NULL + + } + + ~scalarfield(){ + if(loc == CPUmem) free(E); + else cudaFree(E); + } + + /// Calculates the distance between points on the grid + T spacing(){ + T du = rect::X.len() / R[0]; + T dv = rect::Y.len() / R[1]; + return std::min(du, dv); + } + + /// Copy the field array to the GPU, if it isn't already there + void to_gpu(){ + if(loc == GPUmem) return; + else{ + stim::complex* dev_E; + HANDLE_ERROR( cudaMalloc(&dev_E, grid_bytes()) ); //allocate GPU memory + HANDLE_ERROR( cudaMemcpy(dev_E, E, grid_bytes(), cudaMemcpyHostToDevice) ); //copy the field to the GPU + free(E); //free the CPU memory + E = dev_E; //swap pointers + + if(p[0]){ + size_t meshgrid_bytes = size() * sizeof(T); //calculate the number of bytes in each meshgrid + T* dev_X; //allocate variables to store the device meshgrid + T* dev_Y; + T* dev_Z; + HANDLE_ERROR( cudaMalloc(&dev_X, meshgrid_bytes) ); //allocate space for the meshgrid on the device + HANDLE_ERROR( cudaMalloc(&dev_Y, meshgrid_bytes) ); + HANDLE_ERROR( cudaMalloc(&dev_Z, meshgrid_bytes) ); + + HANDLE_ERROR( cudaMemcpy(dev_X, p[0], meshgrid_bytes, cudaMemcpyHostToDevice) ); //copy from the host to the device + HANDLE_ERROR( cudaMemcpy(dev_Y, p[1], meshgrid_bytes, cudaMemcpyHostToDevice) ); + HANDLE_ERROR( cudaMemcpy(dev_Z, p[2], meshgrid_bytes, cudaMemcpyHostToDevice) ); + + free(p[0]); //free device memory + free(p[1]); + free(p[2]); + + p[0] = dev_X; //swap in the new pointers to device memory + p[1] = dev_Y; + p[2] = dev_Z; + } + loc = GPUmem; //set the location flag + } + + } + + /// Copy the field array to the CPU, if it isn't already there + void to_cpu(){ + if(loc == CPUmem) return; + else{ + stim::complex* host_E = (stim::complex*) malloc(grid_bytes()); //allocate space in main memory + HANDLE_ERROR( cudaMemcpy(host_E, E, grid_bytes(), cudaMemcpyDeviceToHost) ); //copy from GPU to CPU + HANDLE_ERROR( cudaFree(E) ); //free device memory + E = host_E; //swap pointers + + //copy a meshgrid has been created + if(p[0]){ + size_t meshgrid_bytes = size() * sizeof(T); //move X to the CPU + T* host_X = (T*) malloc( meshgrid_bytes ); + T* host_Y = (T*) malloc( meshgrid_bytes ); + T* host_Z = (T*) malloc( meshgrid_bytes ); + + HANDLE_ERROR( cudaMemcpy(host_X, p[0], meshgrid_bytes, cudaMemcpyDeviceToHost) ); + HANDLE_ERROR( cudaMemcpy(host_Y, p[1], meshgrid_bytes, cudaMemcpyDeviceToHost) ); + HANDLE_ERROR( cudaMemcpy(host_Z, p[2], meshgrid_bytes, cudaMemcpyDeviceToHost) ); + + HANDLE_ERROR( cudaFree(p[0]) ); + HANDLE_ERROR( cudaFree(p[1]) ); + HANDLE_ERROR( cudaFree(p[2]) ); + + p[0] = host_X; + p[1] = host_Y; + p[2] = host_Z; + } + loc = CPUmem; + } + } + + bool gpu(){ + if(loc == GPUmem) return true; + else return false; + } + + /// Propagate the field along its orthogonal direction by a distance d + void propagate(T d, T k){ + if(loc == CPUmem){ + cpu_scalar_propagate(E, E, X.len(), Y.len(), d, k, R[0], R[1]); + } + else{ + gpu_scalar_propagate(E, E, X.len(), Y.len(), d, k, R[0], R[1]); + } + } + + /// Apply a low pass filter preserving all frequencies lower than or equal to "highest" + // @param highest is the highest frequency passed + void lowpass(T highest){ + cpu_scalar_lowpass(E, E, X.len(), Y.len(), highest, R[0], R[1]); + } + + /// Crop an image based on a given padding parameter (crop out the center) + void crop(size_t padding, stim::scalarfield& cropped){ + size_t Cx = R[0] / (2 * padding + 1); //calculate the size of the cropped image based on the padding value + size_t Cy = R[1] / (2 * padding + 1); + + if(cropped.R[0] != Cx || cropped.R[1] != Cy){ + std::cout<<"Error: cropped field resolution ("<>(cropped.E, E, R[0], R[1], Cx * padding, Cy * padding, Cx, Cy); + } + } + + std::string str(){ + std::stringstream ss; + ss<::str()<* ptr(){ + return E; + } + + T* x(){ return p[0]; } + T* y(){ return p[1]; } + T* z(){ return p[2]; } + + /// Evaluate the cartesian coordinates of each point in the field. The resulting arrays are allocated in the same memory where the field is stored. + void meshgrid(T* X, T* Y, T* Z, locationType location){ + //size_t array_size = sizeof(T) * R[0] * R[1]; + if(location == CPUmem){ + + T du = (T)1.0 / (R[0] - 1); //calculate the spacing between points in the grid + T dv = (T)1.0 / (R[1] - 1); + + size_t ui, vi, i; + stim::vec3 p; + for(vi = 0; vi < R[1]; vi++){ + i = vi * R[0]; + for(ui = 0; ui < R[0]; ui++){ + p = rect::p(ui * du, vi * dv); + X[i] = p[0]; + Y[i] = p[1]; + Z[i] = p[2]; + i++; + } + } + //stim::cpu2image(X, "X.bmp", R[0], R[1], stim::cmBrewer); + //stim::cpu2image(Y, "Y.bmp", R[0], R[1], stim::cmBrewer); + //stim::cpu2image(Z, "Z.bmp", R[0], R[1], stim::cmBrewer); + } + else{ + std::cout<<"GPU allocation of a meshgrid isn't supported yet. You'll have to write kernels to do the calculation."; + exit(1); + } + } + + /// Create a local meshgrid + void meshgrid(){ + if(p[0]) return; //if the p[0] value is not NULL, a meshgrid has already been created + if(loc == CPUmem){ + p[0] = (T*) malloc( size() * sizeof(T) ); + p[1] = (T*) malloc( size() * sizeof(T) ); + p[2] = (T*) malloc( size() * sizeof(T) ); + } + else{ + std::cout<<"GPUmem meshgrid isn't implemented yet."<<<< blocks, threads >>>(image, E, size()); + break; + case complexReal: + cuda_real<<< blocks, threads >>>(image, E, size()); + break; + case complexImaginary: + cuda_imag<<< blocks, threads >>>(image, E, size()); + break; + case complexIntensity: + cuda_intensity<<< blocks, threads >>>(image, E, size()); + break; + default: + std::cout << "ERROR: invalid complex component specified." << std::endl; + exit(1); + } + if (minval == maxval) + stim::gpu2image(image, filename, R[0], R[1], cmap); + else + stim::gpu2image(image, filename, R[0], R[1], minval, maxval, cmap); + HANDLE_ERROR( cudaFree(image) ); + } + else{ + T* image = (T*) malloc( sizeof(T) * size() ); //allocate space for the real image + + switch(type){ //get the specified component from the complex value + case complexMag: + stim::abs(image, E, size()); + break; + case complexReal: + stim::real(image, E, size()); + break; + case complexImaginary: + stim::imag(image, E, size()); + break; + case complexIntensity: + stim::intensity(image, E, size()); + break; + } + if (minval == maxval) + stim::cpu2image(image, filename, R[0], R[1], cmap); //save the resulting image + else + stim::cpu2image(image, filename, R[0], R[1], minval, maxval, cmap); + free(image); //free the real image + } + } + + void image(T* img, stim::complexComponentType type = complexMag){ + if(loc == GPUmem) to_cpu(); //if the field is in the GPU, move it to the CPU + + switch(type){ //get the specified component from the complex value + case complexMag: + stim::abs(img, E, size()); + break; + case complexReal: + stim::real(img, E, size()); + break; + case complexImaginary: + stim::imag(img, E, size()); + break; + case complexIntensity: + stim::intensity(img, E, size()); + break; + } + //stim::cpu2image(image, filename, R[0], R[1], cmap); //save the resulting image + //free(image); //free the real image + } + + //adds the field intensity to the output array (useful for calculating detector response to incoherent fields) + void intensity(T* out){ + if(loc == GPUmem){ + //T* image; + //HANDLE_ERROR( cudaMalloc(&image, sizeof(T) * size()) ); + int threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 blocks( R[0] * R[1] / threads + 1 ); //create a 1D array of blocks + cuda_sum_intensity<<< blocks, threads >>>(out, E, size()); + } + else{ + T* image = (T*) malloc( sizeof(T) * size() ); //allocate space for the real image + stim::intensity(image, E, size()); //calculate the intensity + + size_t N = size(); //calculate the number of elements in the field + for(size_t n = 0; n < N; n++) //for each point in the field + out[n] += image[n]; //add the field intensity to the output image + + free(image); //free the temporary intensity image + } + } + +}; //end class scalarfield +} + +//stream insertion operator +template +std::ostream& operator<<(std::ostream& os, stim::scalarfield& rhs){ + os< + +#include "scalarwave.h" +#include "../math/bessel.h" +#include "../cuda/cudatools/devices.h" +#include + +namespace stim{ + + +/// Calculate the scattering coefficients for a spherical scatterer +template +void B_coefficients(stim::complex* B, T a, T k, stim::complex n, int Nl){ + + //temporary variables + double vm; //allocate space to store the return values for the bessel function calculation + double* j_ka = (double*) malloc( (Nl + 2) * sizeof(double) ); + double* y_ka = (double*) malloc( (Nl + 2) * sizeof(double) ); + double* dj_ka= (double*) malloc( (Nl + 2) * sizeof(double) ); + double* dy_ka= (double*) malloc( (Nl + 2) * sizeof(double) ); + + stim::complex* j_kna = (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* y_kna = (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* dj_kna= (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* dy_kna= (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + + double ka = k * a; //store k*a (argument for spherical bessel and Hankel functions) + stim::complex kna = k * n * a; //store k*n*a (argument for spherical bessel functions and derivatives) + + stim::bessjyv_sph(Nl, ka, vm, j_ka, y_ka, dj_ka, dy_ka); //calculate bessel functions and derivatives for k*a + stim::cbessjyva_sph(Nl, kna, vm, j_kna, y_kna, dj_kna, dy_kna); //calculate complex bessel functions for k*n*a + + stim::complex h_ka, dh_ka; + stim::complex numerator, denominator; + stim::complex i(0, 1); + for(int l = 0; l <= Nl; l++){ + h_ka.r = j_ka[l]; + h_ka.i = y_ka[l]; + dh_ka.r = dj_ka[l]; + dh_ka.i = dy_ka[l]; + + numerator = j_ka[l] * dj_kna[l] * (stim::complex)n - j_kna[l] * dj_ka[l]; + denominator = j_kna[l] * dh_ka - h_ka * dj_kna[l] * (stim::complex)n; + B[l] = (2 * l + 1) * pow(i, l) * numerator / denominator; + } + + //free memory + free(j_ka); free(y_ka); free(dj_ka); free(dy_ka); free(j_kna); free(y_kna); free(dj_kna); free(dy_kna); +} + +template +void A_coefficients(stim::complex* A, T a, T k, stim::complex n, int Nl){ + //temporary variables + double vm; //allocate space to store the return values for the bessel function calculation + double* j_ka = (double*) malloc( (Nl + 2) * sizeof(double) ); + double* y_ka = (double*) malloc( (Nl + 2) * sizeof(double) ); + double* dj_ka= (double*) malloc( (Nl + 2) * sizeof(double) ); + double* dy_ka= (double*) malloc( (Nl + 2) * sizeof(double) ); + + stim::complex* j_kna = (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* y_kna = (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* dj_kna= (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + stim::complex* dy_kna= (stim::complex*) malloc( (Nl + 2) * sizeof(stim::complex) ); + + double ka = k * a; //store k*a (argument for spherical bessel and Hankel functions) + stim::complex kna = k * n * a; //store k*n*a (argument for spherical bessel functions and derivatives) + + stim::bessjyv_sph(Nl, ka, vm, j_ka, y_ka, dj_ka, dy_ka); //calculate bessel functions and derivatives for k*a + stim::cbessjyva_sph(Nl, kna, vm, j_kna, y_kna, dj_kna, dy_kna); //calculate complex bessel functions for k*n*a + + stim::complex h_ka, dh_ka; + stim::complex numerator, denominator; + stim::complex i(0, 1); + for(size_t l = 0; l <= Nl; l++){ + h_ka.r = j_ka[l]; + h_ka.i = y_ka[l]; + dh_ka.r = dj_ka[l]; + dh_ka.i = dy_ka[l]; + + numerator = j_ka[l] * dh_ka - dj_ka[l] * h_ka; + denominator = j_kna[l] * dh_ka - h_ka * dj_kna[l] * (stim::complex)n; + A[l] = (2 * l + 1) * pow(i, l) * numerator / denominator; + } + //free memory + free(j_ka); free(y_ka); free(dj_ka); free(dy_ka); free(j_kna); free(y_kna); free(dj_kna); free(dy_kna); +} + +#define LOCAL_NL 16 + +/// CUDA kernel for calculating the Mie scattering solution given a set of points (x, y, z), a list of plane waves, and a look-up table for Bl*hl +/// @param E (GPU) is the N x N destination scalar field +/// @param N is the number of sample points to evaluate +/// @param x (GPU) is the grid of X coordinates for each point in E +/// @param y (GPU) is the grid of Y coordinates for each point in E +/// @param z (GPU) is the grid of Z coordinates for each point in E +/// @param W (GPU) is an array of coherent scalar plane waves incident on the Mie scatterer +/// @param nW is the number of plane waves to evaluate (sum) +/// @param a is the radius of the Mie scatterer +/// @param n is the complex refractive index of the Mie scatterer +/// @param c is the position of the sphere in (x, y, z) +/// @param hB (GPU) is a look-up table of Hankel functions (equally spaced in distance from the sphere) pre-multiplied with scattering coefficients +/// @param kr_min is the minimum kr value in the hB look-up table (corresponding to the closest point to the sphere) +/// @param dkr is the spacing (in kr) between samples of the hB look-up table +/// @param N_hB is the number of samples in hB +/// @param Nl is the order of the calculation (number of Hankel function orders) +template +__global__ void cuda_scalar_mie_scatter(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t nW, T a, stim::complex n, stim::vec3 c, stim::complex* hB, T r_min, T dr, size_t N_hB, int Nl){ + extern __shared__ stim::complex shared_hB[]; //declare the list of waves in shared memory + + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the sample array (sample point associated with this thread) + if(i >= N) return; //exit if this thread is outside the array + stim::vec3 p; + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + p = p - c; + T r = p.len(); //calculate the distance from the sphere + if(r < a) return; //exit if the point is inside the sphere (we only calculate the internal field) + T fij = (r - r_min)/dr; //FP index into the spherical bessel LUT + size_t ij = (size_t) fij; //convert to an integral index + T alpha = fij - ij; //calculate the fractional portion of the index + size_t n0j = ij * (Nl + 1); //start of the first entry in the LUT + size_t n1j = (ij+1) * (Nl + 1); //start of the second entry in the LUT + + T cos_phi; + T Pl_2, Pl_1, Pl; //declare registers to store the previous two Legendre polynomials + + stim::complex hBl; + stim::complex Ei = 0; //create a register to store the result + int l; + + stim::complex hlBl[LOCAL_NL+1]; //the first LOCAL_NL components are stored in registers for speed + int shared_start = threadIdx.x * (Nl - LOCAL_NL); //wrap up some operations so that they aren't done in the main loops + + //unroll LOCAL_NL + 1 + #pragma unroll 17 //copy the first LOCAL_NL+1 h_l * B_l components to registers + for(l = 0; l <= LOCAL_NL; l++) + hlBl[l] = clerp( hB[n0j + l], hB[n1j + l], alpha ); + + for(l = LOCAL_NL+1; l <= Nl; l++) //copy any additional h_l * B_l components to shared memory + shared_hB[shared_start + (l - (LOCAL_NL+1))] = clerp( hB[n0j + l], hB[n1j + l], alpha ); + + complex e, Ew; + for(size_t w = 0; w < nW; w++){ //for each plane wave + cos_phi = p.norm().dot(W[w].kvec().norm()); //calculate the cosine of the angle between the k vector and the direction from the sphere + Pl_2 = 1; //the Legendre polynomials will be calculated recursively, initialize the first two steps of the recursive relation + Pl_1 = cos_phi; + e = exp(complex(0, W[w].kvec().dot(c))); + Ew = W[w].E() * e; + Ei += Ew * hlBl[0] * Pl_2; //unroll the first two orders using the initial steps of the Legendre recursive relation + Ei += Ew * hlBl[1] * Pl_1; + + //LOCAL_NL - 1 + #pragma unroll 15 //unroll the next LOCAL_NL-1 loops for speed (iterating through the components in the register file) + for(l = 2; l <= LOCAL_NL; l++){ + Pl = ( (2 * (l-1) + 1) * cos_phi * Pl_1 - (l-1) * Pl_2 ) / (l); //calculate the next step in the Legendre polynomial recursive relation (this is where most of the computation occurs) + Ei += Ew * hlBl[l] * Pl; //calculate and sum the current field order + Pl_2 = Pl_1; //shift Pl_1 -> Pl_2 and Pl -> Pl_1 + Pl_1 = Pl; + } + + for(l = LOCAL_NL+1; l <= Nl; l++){ //do the same as above, except for any additional orders that are stored in shared memory (not registers) + Pl = ( (2 * (l-1) + 1) * cos_phi * Pl_1 - (l-1) * Pl_2 ) / (l); //again, this is where most computation in the kernel occurs + Ei += Ew * shared_hB[shared_start + l - LOCAL_NL - 1] * Pl; + Pl_2 = Pl_1; //shift Pl_1 -> Pl_2 and Pl -> Pl_1 + Pl_1 = Pl; + } + } + E[i] += Ei; //copy the result to device memory +} + +///Calculate the scalar Mie scattered field on the GPU when a list of GPU-based pre-multiplied Hankel functions are available +/// @param E (GPU) is the N x N destination scalar field +/// @param N is the number fo elements of the scalar field in each direction +/// @param x (GPU) is the grid of X coordinates for each point in E +/// @param y (GPU) is the grid of Y coordinates for each point in E +/// @param z (GPU) is the grid of Z coordinates for each point in E +/// @param W (GPU) is an array of coherent scalar plane waves incident on the Mie scatterer +/// @param nW is the number of plane waves to evaluate (sum) +/// @param a is the radius of the Mie scatterer +/// @param n is the complex refractive index of the Mie scatterer +/// @param c is the position of the sphere in (x, y, z) +/// @param hB (GPU) is a look-up table of Hankel functions (equally spaced in distance from the sphere) pre-multiplied with scattering coefficients +/// @param kr_min is the minimum kr value in the hB look-up table (corresponding to the closest point to the sphere) +/// @param dkr is the spacing (in kr) between samples of the hB look-up table +/// @param N_hB is the number of samples in hB +/// @param Nl is the order of the calculation (number of Hankel function orders) +template +void gpu_scalar_mie_scatter(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t nW, T a, stim::complex n, stim::vec3 c, stim::complex* hB, T kr_min, T dkr, size_t N_hB, size_t Nl){ + + size_t max_shared_mem = stim::sharedMemPerBlock(); //get the amount of shared memory per block + size_t hBl_array = sizeof(stim::complex) * (Nl + 1); //calculate the number of bytes required to store the LUT corresponding to a single sample in shared memory + int threads = (int)((max_shared_mem / hBl_array) / 32 * 32); //calculate the optimal number of threads per block (make sure it's divisible by the number of warps - 32) + dim3 blocks((unsigned)(N / threads + 1)); //calculate the optimal number of blocks + + size_t shared_mem; + if(Nl <= LOCAL_NL) shared_mem = 0; + else shared_mem = threads * sizeof(stim::complex) * (Nl - LOCAL_NL); //amount of shared memory to allocate + //std::cout<<"shared memory allocated: "<<<< blocks, threads, shared_mem >>>(E, N, x, y, z, W, nW, a, n, c, hB, kr_min, dkr, N_hB, (int)Nl); //call the kernel +} + +template +__global__ void cuda_dist(T* r, T* x, T* y, T* z, size_t N, stim::vec3 c = stim::vec3(0, 0, 0)){ + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the array + if(i >= N) return; //exit if this thread is outside the array + + stim::vec3 p; + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + + r[i] = (p - c).len(); +} + +///Calculate the scalar Mie scattered field on the GPU +/// @param E (GPU) is the N x N destination scalar field +/// @param N is the number of sample points of the scalar field +/// @param x (GPU) is the grid of X coordinates for each point in E +/// @param y (GPU) is the grid of Y coordinates for each point in E +/// @param z (GPU) is the grid of Z coordinates for each point in E +/// @param W (CPU) is an array of coherent scalar plane waves incident on the Mie scatterer +/// @param a is the radius of the Mie scatterer +/// @param n is the complex refractive index of the Mie scatterer +/// @param r_spacing is the minimum distance between r values of the sample points in E (used to calculate look-up tables) +template +void gpu_scalar_mie_scatter(stim::complex* E, size_t N, T* x, T* y, T* z, std::vector> W, T a, stim::complex n, stim::vec3 c = stim::vec3(0, 0, 0), T r_spacing = 0.1){ + + //calculate the necessary number of orders required to represent the scattered field + T k = W[0].kmag(); + + int Nl = (int)ceil(k*a + 4 * cbrt( k * a ) + 2); //calculate the number of orders required to represent the sphere + if(Nl < LOCAL_NL) Nl = LOCAL_NL; //always do at least the minimum number of local operations (kernel optimization) + + //calculate the scattering coefficients for the sphere + stim::complex* B = (stim::complex*) malloc( sizeof(stim::complex) * (Nl + 1) ); //allocate space for the scattering coefficients + B_coefficients(B, a, k, n, Nl); //calculate the scattering coefficients + + // PLANE WAVES + stim::scalarwave* dev_W; //allocate space and copy plane waves + HANDLE_ERROR( cudaMalloc(&dev_W, sizeof(stim::scalarwave) * W.size()) ); + HANDLE_ERROR( cudaMemcpy(dev_W, &W[0], sizeof(stim::scalarwave) * W.size(), cudaMemcpyHostToDevice) ); + + // BESSEL FUNCTION LOOK-UP TABLE + //calculate the distance from the sphere center at each sample point and store the result in dev_r + T* dev_r; //declare the device pointer to hold the distance from the sphere center + HANDLE_ERROR( cudaMalloc(&dev_r, sizeof(T) * N) ); //allocate space for the array of distances + + int threads = stim::maxThreadsPerBlock(); //query the device to find the maximum number of threads per block + dim3 blocks((unsigned)(N / threads + 1)); //calculate the number of blocks necessary to evaluate the total number of sample points N + cuda_dist <<< blocks, threads >>>(dev_r, x, y, z, N, c); //calculate the distance + + //Use the cuBLAS library to find the minimum and maximum distances from the sphere center. This will be used to create a look-up table for the Hankel functions + cublasStatus_t stat; + cublasHandle_t handle; + + stat = cublasCreate(&handle); //create a cuBLAS handle + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS initialization failed\n"); + exit(1); + } + + int i_min, i_max; + stat = cublasIsamin(handle, (int)N, dev_r, 1, &i_min); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate minimum r value.\n"); + exit(1); + } + stat = cublasIsamax(handle, (int)N, dev_r, 1, &i_max); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate maximum r value.\n"); + exit(1); + } + + i_min--; //cuBLAS uses 1-based indexing for Fortran compatibility + i_max--; + T r_min, r_max; //allocate space to store the minimum and maximum values + HANDLE_ERROR( cudaMemcpy(&r_min, dev_r + i_min, sizeof(T), cudaMemcpyDeviceToHost) ); //copy the min and max values from the device to the CPU + HANDLE_ERROR( cudaMemcpy(&r_max, dev_r + i_max, sizeof(T), cudaMemcpyDeviceToHost) ); + + r_min = max(r_min, a); //if the radius of the sphere is larger than r_min, change r_min to a (the scattered field doesn't exist inside the sphere) + + size_t N_hB_lut = (size_t)((r_max - r_min) / r_spacing + 1); //number of values in the look-up table based on the user-specified spacing along r + + //Declare and evaluate variables used to calculate the spherical Bessel functions and store them temporarily on the CPU + double vm; //allocate space to store the return values for the bessel function calculation + double* jv = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* yv = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* djv= (double*) malloc( (Nl + 1) * sizeof(double) ); + double* dyv= (double*) malloc( (Nl + 1) * sizeof(double) ); + + size_t hB_bytes = sizeof(stim::complex) * (Nl+1) * N_hB_lut; //calculate the number of bytes necessary to store the Hankel function LUT + stim::complex* hB_lut = (stim::complex*) malloc(hB_bytes); //pointer to the look-up table + T dr = (r_max - r_min) / (N_hB_lut-1); //calculate the optimal distance between values in the LUT + stim::complex hl; //declare a complex value for the Hankel function result + for(size_t ri = 0; ri < N_hB_lut; ri++){ //for each value in the LUT + stim::bessjyv_sph(Nl, k * (r_min + ri * dr), vm, jv, yv, djv, dyv); //compute the list of spherical bessel functions from [0 Nl] + for(size_t l = 0; l <= Nl; l++){ //for each order + hl.r = (T)jv[l]; //generate the spherical Hankel function from the Bessel functions + hl.i = (T)yv[l]; + + hB_lut[ri * (Nl + 1) + l] = hl * B[l]; //pre-multiply the Hankel function by the scattering coefficients + } + } + + //Copy the pre-multiplied Hankel function look-up table to the GPU - this LUT gives a list of uniformly spaced Hankel function values pre-multiplied by scattering coefficients + stim::complex* dev_hB_lut; + HANDLE_ERROR( cudaMalloc(&dev_hB_lut, hB_bytes) ); + HANDLE_ERROR( cudaMemcpy(dev_hB_lut, hB_lut, hB_bytes, cudaMemcpyHostToDevice) ); + + //calculate the Mie scattering solution on the GPU + gpu_scalar_mie_scatter(E, N, x, y, z, dev_W, W.size(), a, n, c, dev_hB_lut, r_min, dr, N_hB_lut, Nl); + + //HANDLE_ERROR(cudaMemcpy(E, E, N * sizeof(stim::complex), cudaMemcpyDeviceToHost)); //copy the field from device memory + + HANDLE_ERROR(cudaFree(dev_hB_lut)); + HANDLE_ERROR(cudaFree(dev_r)); + HANDLE_ERROR(cudaFree(dev_W)); + +} +/// Calculate the scalar Mie solution for the scattered field produced by a single plane wave + +/// @param E is a pointer to the destination field values +/// @param N is the number of points used to calculate the field +/// @param x is an array of x coordinates for each point, specified relative to the sphere (x = NULL assumes all zeros) +/// @param y is an array of y coordinates for each point, specified relative to the sphere (y = NULL assumes all zeros) +/// @param z is an array of z coordinates for each point, specified relative to the sphere (z = NULL assumes all zeros) +/// @param W is an array of planewaves that will be scattered +/// @param a is the radius of the sphere +/// @param n is the complex refractive index of the sphere +template +void cpu_scalar_mie_scatter(stim::complex* E, size_t N, T* x, T* y, T* z, std::vector> W, T a, stim::complex n, stim::vec3 c = stim::vec3(0, 0, 0)){ + + +/*#ifdef CUDA_FOUND + stim::complex* dev_E; //allocate space for the field + cudaMalloc(&dev_E, N * sizeof(stim::complex)); + cudaMemcpy(dev_E, E, N * sizeof(stim::complex), cudaMemcpyHostToDevice); + //cudaMemset(dev_F, 0, N * sizeof(stim::complex)); //set the field to zero (necessary because a sum is used) + + // COORDINATES + T* dev_x = NULL; //allocate space and copy the X coordinate (if specified) + if(x != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_x, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_x, x, N * sizeof(T), cudaMemcpyHostToDevice)); + } + T* dev_y = NULL; //allocate space and copy the Y coordinate (if specified) + if(y != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_y, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_y, y, N * sizeof(T), cudaMemcpyHostToDevice)); + } + T* dev_z = NULL; //allocate space and copy the Z coordinate (if specified) + if(z != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_z, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_z, z, N * sizeof(T), cudaMemcpyHostToDevice)); + } + + gpu_scalar_mie_scatter(dev_E, N, dev_x, dev_y, dev_z, W, a, n, c, r_spacing); + + if(x != NULL) cudaFree(dev_x); //free everything + if(y != NULL) cudaFree(dev_y); + if(z != NULL) cudaFree(dev_z); + cudaFree(dev_E); +#else +*/ + + //calculate the necessary number of orders required to represent the scattered field + T k = W[0].kmag(); + + int Nl = (int)ceil(k*a + 4 * cbrt( k * a ) + 2); + if(Nl < LOCAL_NL) Nl = LOCAL_NL; //always do at least the minimum number of local operations (kernel optimization) + //std::cout<<"Nl: "<* B = (stim::complex*) malloc( sizeof(stim::complex) * (Nl + 1) ); //allocate space for the scattering coefficients + B_coefficients(B, a, k, n, Nl); + + //allocate space to store the bessel function call results + double vm; + double* j_kr = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* y_kr = (double*) malloc( (Nl + 1) * sizeof(double) ); + double* dj_kr= (double*) malloc( (Nl + 1) * sizeof(double) ); + double* dy_kr= (double*) malloc( (Nl + 1) * sizeof(double) ); + + T* P = (T*) malloc( (Nl + 1) * sizeof(T) ); + + T r, kr, cos_phi; + stim::complex h; + stim::complex Ew; + for(size_t i = 0; i < N; i++){ + stim::vec3 p; //declare a 3D point + + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + p = p - c; + r = p.len(); + if(r >= a){ + for(size_t w = 0; w < W.size(); w++){ + Ew = W[w].E() * exp(stim::complex(0, W[w].kvec().dot(c))); + kr = p.len() * W[w].kmag(); //calculate k*r + stim::bessjyv_sph(Nl, kr, vm, j_kr, y_kr, dj_kr, dy_kr); + cos_phi = p.norm().dot(W[w].kvec().norm()); //calculate the cosine of the angle from the propagating direction + stim::legendre(Nl, cos_phi, P); + + for(size_t l = 0; l <= Nl; l++){ + h.r = j_kr[l]; + h.i = y_kr[l]; + E[i] += Ew * B[l] * h * P[l]; + } + } + } + } +//#endif +} + +template +void cpu_scalar_mie_scatter(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave w, T a, stim::complex n, stim::vec3 c = stim::vec3(0, 0, 0), T r_spacing = 0.1){ + std::vector< stim::scalarwave > W(1, w); + cpu_scalar_mie_scatter(E, N, x, y, z, W, a, n, c, r_spacing); +} + +template +__global__ void cuda_scalar_mie_internal(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t nW, T a, stim::complex n, stim::complex* jA, T r_min, T dr, size_t N_jA, int Nl){ + extern __shared__ stim::complex shared_jA[]; //declare the list of waves in shared memory + + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the array + if(i >= N) return; //exit if this thread is outside the array + stim::vec3 p; + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + + T r = p.len(); //calculate the distance from the sphere + if(r >= a) return; //exit if the point is inside the sphere (we only calculate the internal field) + T fij = (r - r_min)/dr; //FP index into the spherical bessel LUT + size_t ij = (size_t) fij; //convert to an integral index + T alpha = fij - ij; //calculate the fractional portion of the index + size_t n0j = ij * (Nl + 1); //start of the first entry in the LUT + size_t n1j = (ij+1) * (Nl + 1); //start of the second entry in the LUT + + T cos_phi; + T Pl_2, Pl_1, Pl; //declare registers to store the previous two Legendre polynomials + + stim::complex jAl; + stim::complex Ei = 0; //create a register to store the result + int l; + + stim::complex jlAl[LOCAL_NL+1]; //the first LOCAL_NL components are stored in registers for speed + int shared_start = threadIdx.x * (Nl - LOCAL_NL); //wrap up some operations so that they aren't done in the main loops + + #pragma unroll LOCAL_NL+1 //copy the first LOCAL_NL+1 h_l * B_l components to registers + for(l = 0; l <= LOCAL_NL; l++) + jlAl[l] = clerp( jA[n0j + l], jA[n1j + l], alpha ); + + for(l = LOCAL_NL+1; l <= Nl; l++) //copy any additional h_l * B_l components to shared memory + shared_jA[shared_start + (l - (LOCAL_NL+1))] = clerp( jA[n0j + l], jA[n1j + l], alpha ); + + for(size_t w = 0; w < nW; w++){ //for each plane wave + if(r == 0) cos_phi = 0; + else + cos_phi = p.norm().dot(W[w].kvec().norm()); //calculate the cosine of the angle between the k vector and the direction from the sphere + Pl_2 = 1; //the Legendre polynomials will be calculated recursively, initialize the first two steps of the recursive relation + Pl_1 = cos_phi; + Ei += W[w].E() * jlAl[0] * Pl_2; //unroll the first two orders using the initial steps of the Legendre recursive relation + Ei += W[w].E() * jlAl[1] * Pl_1; + + #pragma unroll LOCAL_NL-1 //unroll the next LOCAL_NL-1 loops for speed (iterating through the components in the register file) + for(l = 2; l <= LOCAL_NL; l++){ + Pl = ( (2 * (l-1) + 1) * cos_phi * Pl_1 - (l-1) * Pl_2 ) / (l); //calculate the next step in the Legendre polynomial recursive relation (this is where most of the computation occurs) + Ei += W[w].E() * jlAl[l] * Pl; //calculate and sum the current field order + Pl_2 = Pl_1; //shift Pl_1 -> Pl_2 and Pl -> Pl_1 + Pl_1 = Pl; + } + + for(l = LOCAL_NL+1; l <= Nl; l++){ //do the same as above, except for any additional orders that are stored in shared memory (not registers) + Pl = ( (2 * (l-1) + 1) * cos_phi * Pl_1 - (l-1) * Pl_2 ) / (l); //again, this is where most computation in the kernel occurs + Ei += W[w].E() * shared_jA[shared_start + l - LOCAL_NL - 1] * Pl; + Pl_2 = Pl_1; //shift Pl_1 -> Pl_2 and Pl -> Pl_1 + Pl_1 = Pl; + } + } + E[i] = Ei; //copy the result to device memory +} + +template +void gpu_scalar_mie_internal(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t nW, T a, stim::complex n, stim::complex* jA, T r_min, T dr, size_t N_jA, size_t Nl){ + + size_t max_shared_mem = stim::sharedMemPerBlock(); + size_t hBl_array = sizeof(stim::complex) * (Nl + 1); + //std::cout<<"hl*Bl array size: "<) * (Nl - LOCAL_NL); //amount of shared memory to allocate + //std::cout<<"shared memory allocated: "<<<< blocks, threads, shared_mem >>>(E, N, x, y, z, W, nW, a, n, jA, r_min, dr, N_jA, (int)Nl); //call the kernel +} + +/// Calculate the scalar Mie solution for the internal field produced by a single plane wave scattered by a sphere + +/// @param E is a pointer to the destination field values +/// @param N is the number of points used to calculate the field +/// @param x is an array of x coordinates for each point, specified relative to the sphere (x = NULL assumes all zeros) +/// @param y is an array of y coordinates for each point, specified relative to the sphere (y = NULL assumes all zeros) +/// @param z is an array of z coordinates for each point, specified relative to the sphere (z = NULL assumes all zeros) +/// @param w is a planewave that will be scattered +/// @param a is the radius of the sphere +/// @param n is the complex refractive index of the sphere +template +void cpu_scalar_mie_internal(stim::complex* E, size_t N, T* x, T* y, T* z, std::vector< stim::scalarwave > W, T a, stim::complex n, stim::vec3 c = stim::vec3(0, 0, 0)){ +//calculate the necessary number of orders required to represent the scattered field + T k = W[0].kmag(); + + int Nl = (int)ceil(k*a + 4 * cbrt( k * a ) + 2); + if(Nl < LOCAL_NL) Nl = LOCAL_NL; //always do at least the minimum number of local operations (kernel optimization) + //std::cout<<"Nl: "<* A = (stim::complex*) malloc( sizeof(stim::complex) * (Nl + 1) ); //allocate space for the scattering coefficients + A_coefficients(A, a, k, n, Nl); + +/*#ifdef CUDA_FOUND + stim::complex* dev_E; //allocate space for the field + cudaMalloc(&dev_E, N * sizeof(stim::complex)); + cudaMemcpy(dev_E, E, N * sizeof(stim::complex), cudaMemcpyHostToDevice); + //cudaMemset(dev_F, 0, N * sizeof(stim::complex)); //set the field to zero (necessary because a sum is used) + + // COORDINATES + T* dev_x = NULL; //allocate space and copy the X coordinate (if specified) + if(x != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_x, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_x, x, N * sizeof(T), cudaMemcpyHostToDevice)); + } + T* dev_y = NULL; //allocate space and copy the Y coordinate (if specified) + if(y != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_y, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_y, y, N * sizeof(T), cudaMemcpyHostToDevice)); + } + T* dev_z = NULL; //allocate space and copy the Z coordinate (if specified) + if(z != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_z, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_z, z, N * sizeof(T), cudaMemcpyHostToDevice)); + } + + // PLANE WAVES + stim::scalarwave* dev_W; //allocate space and copy plane waves + HANDLE_ERROR( cudaMalloc(&dev_W, sizeof(stim::scalarwave) * W.size()) ); + HANDLE_ERROR( cudaMemcpy(dev_W, &W[0], sizeof(stim::scalarwave) * W.size(), cudaMemcpyHostToDevice) ); + + // BESSEL FUNCTION LOOK-UP TABLE + //calculate the distance from the sphere center + T* dev_r; + HANDLE_ERROR( cudaMalloc(&dev_r, sizeof(T) * N) ); + + int threads = stim::maxThreadsPerBlock(); + dim3 blocks((unsigned)(N / threads + 1)); + cuda_dist <<< blocks, threads >>>(dev_r, dev_x, dev_y, dev_z, N); + + //Find the minimum and maximum values of r + cublasStatus_t stat; + cublasHandle_t handle; + + stat = cublasCreate(&handle); //create a cuBLAS handle + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS initialization failed\n"); + exit(1); + } + + int i_min, i_max; + stat = cublasIsamin(handle, (int)N, dev_r, 1, &i_min); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate minimum r value.\n"); + exit(1); + } + stat = cublasIsamax(handle, (int)N, dev_r, 1, &i_max); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate maximum r value.\n"); + exit(1); + } + cublasDestroy(handle); //destroy the CUBLAS handle + + i_min--; //cuBLAS uses 1-based indexing for Fortran compatibility + i_max--; + T r_min, r_max; //allocate space to store the minimum and maximum values + HANDLE_ERROR( cudaMemcpy(&r_min, dev_r + i_min, sizeof(T), cudaMemcpyDeviceToHost) ); //copy the min and max values from the device to the CPU + HANDLE_ERROR( cudaMemcpy(&r_max, dev_r + i_max, sizeof(T), cudaMemcpyDeviceToHost) ); + + r_max = min(r_max, a); //the internal field doesn't exist outside of the sphere + + size_t N_jA_lut = (size_t)((r_max - r_min) / r_spacing + 1); + + //temporary variables + double vm; //allocate space to store the return values for the bessel function calculation + stim::complex* jv = (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* yv = (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* djv= (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* dyv= (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + + size_t jA_bytes = sizeof(stim::complex) * (Nl+1) * N_jA_lut; + stim::complex* jA_lut = (stim::complex*) malloc(jA_bytes); //pointer to the look-up table + T dr = (r_max - r_min) / (N_jA_lut-1); //distance between values in the LUT + //std::cout<<"LUT jl bytes: "< hl; + stim::complex nd = (stim::complex)n; + for(size_t ri = 0; ri < N_jA_lut; ri++){ //for each value in the LUT + stim::cbessjyva_sph(Nl, nd * k * (r_min + ri * dr), vm, jv, yv, djv, dyv); //compute the list of spherical bessel functions from [0 Nl] + for(size_t l = 0; l <= Nl; l++){ //for each order + jA_lut[ri * (Nl + 1) + l] = (stim::complex)(jv[l] * (stim::complex)A[l]); //store the bessel function result + } + } + + //Allocate device memory and copy everything to the GPU + stim::complex* dev_jA_lut; + HANDLE_ERROR( cudaMalloc(&dev_jA_lut, jA_bytes) ); + HANDLE_ERROR( cudaMemcpy(dev_jA_lut, jA_lut, jA_bytes, cudaMemcpyHostToDevice) ); + + gpu_scalar_mie_internal(dev_E, N, dev_x, dev_y, dev_z, dev_W, W.size(), a, n, dev_jA_lut, r_min, dr, N_jA_lut, Nl); + + cudaMemcpy(E, dev_E, N * sizeof(stim::complex), cudaMemcpyDeviceToHost); //copy the field from device memory + + if(x != NULL) cudaFree(dev_x); //free everything + if(y != NULL) cudaFree(dev_y); + if(z != NULL) cudaFree(dev_z); + HANDLE_ERROR( cudaFree(dev_jA_lut) ); + HANDLE_ERROR( cudaFree(dev_E) ); + HANDLE_ERROR( cudaFree(dev_W) ); + HANDLE_ERROR( cudaFree(dev_r) ); + HANDLE_ERROR( cudaFree(dev_E) ); +#else +*/ + //allocate space to store the bessel function call results + double vm; + stim::complex* j_knr = (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* y_knr = (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* dj_knr= (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + stim::complex* dy_knr= (stim::complex*) malloc( (Nl + 1) * sizeof(stim::complex) ); + + T* P = (T*) malloc( (Nl + 1) * sizeof(T) ); + + T r, cos_phi; + stim::complex knr; + stim::complex h; + stim::complex Ew; + for(size_t i = 0; i < N; i++){ + stim::vec3 p; //declare a 3D point + + (x == NULL) ? p[0] = 0 : p[0] = x[i]; // test for NULL values and set positions + (y == NULL) ? p[1] = 0 : p[1] = y[i]; + (z == NULL) ? p[2] = 0 : p[2] = z[i]; + p = p - c; + r = p.len(); + if(r < a){ + E[i] = 0; + for(size_t w = 0; w < W.size(); w++){ + Ew = W[w].E() * exp(stim::complex(0, W[w].kvec().dot(c))); + knr = (stim::complex)n * p.len() * W[w].kmag(); //calculate k*n*r + + stim::cbessjyva_sph(Nl, knr, vm, j_knr, y_knr, dj_knr, dy_knr); + if(r == 0) + cos_phi = 0; + else + cos_phi = p.norm().dot(W[w].kvec().norm()); //calculate the cosine of the angle from the propagating direction + stim::legendre(Nl, cos_phi, P); + + for(size_t l = 0; l <= Nl; l++){ + E[i] += Ew * A[l] * (stim::complex)j_knr[l] * P[l]; + } + } + } + } +//#endif +} + +template +void cpu_scalar_mie_internal(stim::complex* E, size_t N, T* x, T* y, T* z, stim::scalarwave w, T a, stim::complex n, stim::vec3 c = stim::vec3(0, 0, 0)){ + std::vector< stim::scalarwave > W(1, w); + cpu_scalar_mie_internal(E, N, x, y, z, W, a, n, c); +} + + +/// Class stim::scalarmie represents a scalar Mie scattering model that can be used to calculate the fields produced by a scattering sphere. +template +class scalarmie +{ +public: + T radius; //radius of the scattering sphere + stim::complex n; //refractive index of the scattering sphere + vec3 c; //position of the sphere in space + +public: + + scalarmie() { //default constructor + radius = 0.5; + n = stim::complex(1.4, 0.0); + c = stim::vec3(0, 0, 0); + } + + scalarmie(T r, stim::complex ri, stim::vec3 center = stim::vec3(0, 0, 0)){ + radius = r; + n = ri; + c = center; + //c = stim::vec3(2, 1, 0); + } + + //void sum_scat(stim::scalarfield& E, T* X, T* Y, T* Z, stim::scalarbeam b, int samples = 1000){ + // std::vector< stim::scalarwave > wave_array = b.mc(samples); //decompose the beam into an array of plane waves + // stim::cpu_scalar_mie_scatter(E.ptr(), E.size(), X, Y, Z, wave_array, radius, n, E.spacing()); + //} + + //void sum_intern(stim::scalarfield& E, T* X, T* Y, T* Z, stim::scalarbeam b, int samples = 1000){ + // std::vector< stim::scalarwave > wave_array = b.mc(samples); //decompose the beam into an array of plane waves + // stim::cpu_scalar_mie_internal(E.ptr(), E.size(), X, Y, Z, wave_array, radius, n, E.spacing()); + //} + + //void eval(stim::scalarfield& E, T* X, T* Y, T* Z, stim::scalarbeam b, int order = 500, int samples = 1000){ + // b.eval(E, X, Y, Z, order); //evaluate the incident field using a plane wave expansion + // std::vector< stim::scalarwave > wave_array = b.mc(samples); //decompose the beam into an array of plane waves + // sum_scat(E, X, Y, Z, b, samples); + // sum_intern(E, X, Y, Z, b, samples); + //} + + void eval(stim::scalarfield& E, stim::scalarbeam b, int order = 500, int samples = 1000){ + + E.meshgrid(); + b.eval(E, order); + + std::vector< stim::scalarwave > wave_array = b.mc(samples); //decompose the beam into an array of plane waves + + if(E.gpu()){ + stim::gpu_scalar_mie_scatter(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, c, E.spacing()); + } + else{ + stim::cpu_scalar_mie_scatter(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, E.spacing()); + stim::cpu_scalar_mie_internal(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, E.spacing()); + } + } + +}; //end stim::scalarmie + +template +class scalarcluster : public std::vector< scalarmie > { + +public: + + void eval(stim::scalarfield& E, stim::scalarbeam b, int order = 500, int samples = 1000) { + E.meshgrid(); + b.eval(E, order); + + std::vector< stim::scalarwave > wave_array = b.mc(samples); //decompose the beam into an array of plane waves + + T radius; + stim::complex n; + stim::vec3 c; + for (size_t si = 0; si < std::vector< scalarmie >::size(); si++) { //for each sphere in the cluster + radius = std::vector< scalarmie >::at(si).radius; + n = std::vector< scalarmie >::at(si).n; + c = std::vector< scalarmie >::at(si).c; + if (E.gpu()) { + stim::gpu_scalar_mie_scatter(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, c, E.spacing()); + } + else { + stim::cpu_scalar_mie_scatter(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, c); + stim::cpu_scalar_mie_internal(E.ptr(), E.size(), E.x(), E.y(), E.z(), wave_array, radius, n, c); + } + } + } +}; + +} //end namespace stim + +#endif \ No newline at end of file diff --git a/tira/optics/scalarwave.h b/tira/optics/scalarwave.h new file mode 100644 index 0000000..a6dd7ec --- /dev/null +++ b/tira/optics/scalarwave.h @@ -0,0 +1,367 @@ +#ifndef STIM_SCALARWAVE_H +#define STIM_SCALARWAVE_H + + +#include +#include +#include + +//#include "../math/vector.h" +#include "../math/vec3.h" +#include "../math/quaternion.h" +#include "../math/constants.h" +#include "../math/plane.h" +#include "../math/complex.h" + +//CUDA +#include "../cuda/cudatools/devices.h" +#include "../cuda/cudatools/error.h" +#include "../cuda/sharedmem.cuh" + +namespace stim{ + +template +class scalarwave{ + +public: + + stim::vec3 k; //k-vector, pointed in propagation direction with magnitude |k| = tau / lambda = 2pi / lambda + stim::complex E0; //amplitude + + /// Bend a plane wave via refraction, given that the new propagation direction is known + CUDA_CALLABLE scalarwave bend(stim::vec3 kn) const{ + return scalarwave(kn.norm() * kmag(), E0); + } + +public: + + ///constructor: create a plane wave propagating along k + CUDA_CALLABLE scalarwave(vec3 kvec = stim::vec3(0, 0, (T)stim::TAU), complex E = 1){ + k = kvec; + E0 = E; + } + + CUDA_CALLABLE scalarwave(T kx, T ky, T kz, complex E = 1){ + k = vec3(kx, ky, kz); + E0 = E; + } + + ///multiplication operator: scale E0 + CUDA_CALLABLE scalarwave & operator* (const T & rhs){ + E0 = E0 * rhs; + return *this; + } + + CUDA_CALLABLE T lambda() const{ + return stim::TAU / k.len(); + } + + CUDA_CALLABLE T kmag() const{ + return k.len(); + } + + CUDA_CALLABLE complex E(){ + return E0; + } + + CUDA_CALLABLE vec3 kvec(){ + return k; + } + + /// calculate the value of the field produced by the plane wave given a three-dimensional position + CUDA_CALLABLE complex pos(T x, T y, T z){ + return pos( stim::vec3(x, y, z) ); + } + + CUDA_CALLABLE complex pos(vec3 p = vec3(0, 0, 0)){ + return E0 * exp(complex(0, k.dot(p))); + } + + //scales k based on a transition from material ni to material nt + CUDA_CALLABLE scalarwave n(T ni, T nt){ + return scalarwave(k * (nt / ni), E0); + } + + CUDA_CALLABLE scalarwave refract(stim::vec3 kn) const{ + return bend(kn); + } + + /// Calculate the result of a plane wave hitting an interface between two refractive indices + + /// @param P is a plane representing the position and orientation of the surface + /// @param n0 is the refractive index outside of the surface (in the direction of the normal) + /// @param n1 is the refractive index inside the surface (in the direction away from the normal) + /// @param r is the reflected component of the plane wave + /// @param t is the transmitted component of the plane wave + void scatter(stim::plane P, T n0, T n1, scalarwave &r, scalarwave &t){ + scatter(P, n1/n0, r, t); + } + + /// Calculate the scattering result when nr = n1/n0 + + /// @param P is a plane representing the position and orientation of the surface + /// @param r is the ration n1/n0 + /// @param n1 is the refractive index inside the surface (in the direction away from the normal) + /// @param r is the reflected component of the plane wave + /// @param t is the transmitted component of the plane wave + void scatter(stim::plane P, T nr, scalarwave &r, scalarwave &t){ + /* + int facing = P.face(k); //determine which direction the plane wave is coming in + + if(facing == -1){ //if the wave hits the back of the plane, invert the plane and nr + P = P.flip(); //flip the plane + nr = 1/nr; //invert the refractive index (now nr = n0/n1) + } + + //use Snell's Law to calculate the transmitted angle + T cos_theta_i = k.norm().dot(-P.norm()); //compute the cosine of theta_i + T theta_i = acos(cos_theta_i); //compute theta_i + T sin_theta_t = (1/nr) * sin(theta_i); //compute the sine of theta_t using Snell's law + T theta_t = asin(sin_theta_t); //compute the cosine of theta_t + + bool tir = false; //flag for total internal reflection + if(theta_t != theta_t){ + tir = true; + theta_t = stim::PI / (T)2; + } + + //handle the degenerate case where theta_i is 0 (the plane wave hits head-on) + if(theta_i == 0){ + T rp = (1 - nr) / (1 + nr); //compute the Fresnel coefficients + T tp = 2 / (1 + nr); + vec3 kr = -k; + vec3 kt = k * nr; //set the k vectors for theta_i = 0 + vec3< complex > Er = E0 * rp; //compute the E vectors + vec3< complex > Et = E0 * tp; + T phase_t = P.p().dot(k - kt); //compute the phase offset + T phase_r = P.p().dot(k - kr); + + //create the plane waves + r = planewave(kr, Er, phase_r); + t = planewave(kt, Et, phase_t); + return; + } + + + //compute the Fresnel coefficients + T rp, rs, tp, ts; + rp = tan(theta_t - theta_i) / tan(theta_t + theta_i); + rs = sin(theta_t - theta_i) / sin(theta_t + theta_i); + + if(tir){ + tp = ts = 0; + } + else{ + tp = ( 2 * sin(theta_t) * cos(theta_i) ) / ( sin(theta_t + theta_i) * cos(theta_t - theta_i) ); + ts = ( 2 * sin(theta_t) * cos(theta_i) ) / sin(theta_t + theta_i); + } + + //compute the coordinate space for the plane of incidence + vec3 z_hat = -P.norm(); + vec3 y_hat = P.parallel(k).norm(); + vec3 x_hat = y_hat.cross(z_hat).norm(); + + //compute the k vectors for r and t + vec3 kr, kt; + kr = ( y_hat * sin(theta_i) - z_hat * cos(theta_i) ) * kmag(); + kt = ( y_hat * sin(theta_t) + z_hat * cos(theta_t) ) * kmag() * nr; + + //compute the magnitude of the p- and s-polarized components of the incident E vector + complex Ei_s = E0.dot(x_hat); + int sgn = E0.dot(y_hat).sgn(); + vec3< complex > cx_hat = x_hat; + complex Ei_p = ( E0 - cx_hat * Ei_s ).len() * sgn; + //compute the magnitude of the p- and s-polarized components of the reflected E vector + complex Er_s = Ei_s * rs; + complex Er_p = Ei_p * rp; + //compute the magnitude of the p- and s-polarized components of the transmitted E vector + complex Et_s = Ei_s * ts; + complex Et_p = Ei_p * tp; + + //compute the reflected E vector + vec3< complex > Er = vec3< complex >(y_hat * cos(theta_i) + z_hat * sin(theta_i)) * Er_p + cx_hat * Er_s; + //compute the transmitted E vector + vec3< complex > Et = vec3< complex >(y_hat * cos(theta_t) - z_hat * sin(theta_t)) * Et_p + cx_hat * Et_s; + + T phase_t = P.p().dot(k - kt); + T phase_r = P.p().dot(k - kr); + + //create the plane waves + r.k = kr; + r.E0 = Er * exp( complex(0, phase_r) ); + + t.k = kt; + t.E0 = Et * exp( complex(0, phase_t) ); + */ + } + + std::string str() + { + std::stringstream ss; + ss<<"Plane Wave:"< +__global__ void cuda_scalarwave(stim::complex* F, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t n_waves){ + extern __shared__ stim::scalarwave shared_W[]; //declare the list of waves in shared memory + + stim::cuda::threadedMemcpy(shared_W, W, n_waves, threadIdx.x, blockDim.x); //copy the plane waves into shared memory for faster access + __syncthreads(); //synchronize threads to insure all data is copied + + size_t i = blockIdx.x * blockDim.x + threadIdx.x; //get the index into the array + if(i >= N) return; //exit if this thread is outside the array + T px, py, pz; + (x == NULL) ? px = 0 : px = x[i]; // test for NULL values and set positions + (y == NULL) ? py = 0 : py = y[i]; + (z == NULL) ? pz = 0 : pz = z[i]; + + stim::complex f = 0; //create a register to store the result + for(size_t w = 0; w < n_waves; w++) + f += shared_W[w].pos(px, py, pz); //evaluate the plane wave + F[i] += f; //copy the result to device memory +} + +/// evaluate a scalar wave at several points, where all arrays are on the GPU +template +void gpu_scalarwave(stim::complex* F, size_t N, T* x, T* y, T* z, stim::scalarwave w){ + + int threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 blocks(N / threads + 1); //calculate the optimal number of blocks + cuda_scalarwave<<< blocks, threads >>>(F, N, x, y, z, w); //call the kernel +} + +template +void gpu_scalarwaves(stim::complex* F, size_t N, T* x, T* y, T* z, stim::scalarwave* W, size_t nW){ + + size_t wave_bytes = sizeof(stim::scalarwave); + size_t shared_bytes = stim::sharedMemPerBlock(); //calculate the maximum amount of shared memory available + size_t max_batch = shared_bytes / wave_bytes; //calculate number of plane waves that will fit into shared memory + size_t batch_bytes = min(nW, max_batch) * wave_bytes; //initialize the batch size (in bytes) to the maximum batch required + + stim::scalarwave* batch_W; + HANDLE_ERROR(cudaMalloc(&batch_W, batch_bytes)); //allocate memory for a single batch of plane waves + + int threads = stim::maxThreadsPerBlock(); //get the maximum number of threads per block for the CUDA device + dim3 blocks((unsigned)(N / threads + 1)); //calculate the optimal number of blocks + + size_t batch_size; //declare a variable to store the size of the current batch + size_t waves_processed = 0; //initialize the number of waves processed to zero + while(waves_processed < nW){ //while there are still waves to be processed + batch_size = std::min(max_batch, nW - waves_processed); //process either a whole batch, or whatever is left + batch_bytes = batch_size * sizeof(stim::scalarwave); + HANDLE_ERROR(cudaMemcpy(batch_W, W + waves_processed, batch_bytes, cudaMemcpyDeviceToDevice)); //copy the plane waves into global memory + cuda_scalarwave<<< blocks, threads, batch_bytes >>>(F, N, x, y, z, batch_W, batch_size); //call the kernel + waves_processed += batch_size; //increment the counter indicating how many waves have been processed + } + cudaFree(batch_W); +} + +/// Sums a series of coherent plane waves at a specified point +/// @param field is the output array of field values corresponding to each input point +/// @param x is an array of x coordinates for the field point +/// @param y is an array of y coordinates for the field point +/// @param z is an array of z coordinates for the field point +/// @param N is the number of points in the input and output arrays +/// @param lambda is the wavelength (all coherent waves are assumed to have the same wavelength) +/// @param A is the list of amplitudes for each wave +/// @param S is the list of propagation directions for each wave +template +void cpu_scalarwaves(stim::complex* F, size_t N, T* x, T* y, T* z, std::vector< stim::scalarwave > W){ + size_t S = W.size(); //store the number of waves +#ifdef __CUDACC__ + stim::complex* dev_F; //allocate space for the field + cudaMalloc(&dev_F, N * sizeof(stim::complex)); + cudaMemcpy(dev_F, F, N * sizeof(stim::complex), cudaMemcpyHostToDevice); + //cudaMemset(dev_F, 0, N * sizeof(stim::complex)); //set the field to zero (necessary because a sum is used) + + T* dev_x = NULL; //allocate space and copy the X coordinate (if specified) + if(x != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_x, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_x, x, N * sizeof(T), cudaMemcpyHostToDevice)); + } + + T* dev_y = NULL; //allocate space and copy the Y coordinate (if specified) + if(y != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_y, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_y, y, N * sizeof(T), cudaMemcpyHostToDevice)); + } + + T* dev_z = NULL; //allocate space and copy the Z coordinate (if specified) + if(z != NULL){ + HANDLE_ERROR(cudaMalloc(&dev_z, N * sizeof(T))); + HANDLE_ERROR(cudaMemcpy(dev_z, z, N * sizeof(T), cudaMemcpyHostToDevice)); + } + + stim::scalarwave* dev_W; + HANDLE_ERROR( cudaMalloc(&dev_W, sizeof(stim::scalarwave) * W.size()) ); + HANDLE_ERROR( cudaMemcpy(dev_W, &W[0], sizeof(stim::scalarwave) * W.size(), cudaMemcpyHostToDevice) ); + + gpu_scalarwaves(dev_F, N, dev_x, dev_y, dev_z, dev_W, W.size()); + + cudaMemcpy(F, dev_F, N * sizeof(stim::complex), cudaMemcpyDeviceToHost); //copy the field from device memory + + if(x != NULL) cudaFree(dev_x); //free everything + if(y != NULL) cudaFree(dev_y); + if(z != NULL) cudaFree(dev_z); + cudaFree(dev_F); +#else + memset(F, 0, N * sizeof(stim::complex)); + T px, py, pz; + for(size_t i = 0; i < N; i++){ // for each element in the array + (x == NULL) ? px = 0 : px = x[i]; // test for NULL values + (y == NULL) ? py = 0 : py = y[i]; + (z == NULL) ? pz = 0 : pz = z[i]; + + for(size_t s = 0; s < S; s++){ + F[i] += w_array[s].pos(px, py, pz); //sum all plane waves at this point + } + } +#endif +} + +template +void cpu_scalarwave(stim::complex* F, size_t N, T* x, T* y, T* z, stim::scalarwave w){ + std::vector< stim::scalarwave > w_array(1, w); + cpu_scalarwaves(F, N, x, y, z, w_array); +} + +template +void cpu_scalarwaves(stim::complex* F, size_t N, T* x, T* y, T* z, stim::scalarwave w){ + std::vector< stim::scalarwave > w_array(1, w); + cpu_scalarwaves(F, N, x, y, z, w_array); +} + + +/// Sums a series of coherent plane waves at a specified point +/// @param x is the x coordinate of the field point +/// @param y is the y coordinate of the field point +/// @param z is the z coordinate of the field point +/// @param lambda is the wavelength (all coherent waves are assumed to have the same wavelength) +/// @param A is the list of amplitudes for each wave +/// @param S is the list of propagation directions for each wave +template +CUDA_CALLABLE stim::complex cpu_scalarwaves(T x, T y, T z, std::vector< stim::scalarwave > W){ + size_t N = W.size(); //get the number of plane wave samples + stim::complex field(0, 0); //initialize the field to zero (0) + stim::vec3 k; //allocate space for the direction vector + for(size_t i = 0; i < N; i++){ + field += W[i].pos(x, y, z); + } + return field; +} + +} //end namespace stim + +template +std::ostream& operator<<(std::ostream& os, stim::scalarwave p) +{ + os< + +namespace stim{ + +template +class beam : public planewave

+{ +public: + enum beam_type {Uniform, Bartlett, Hamming, Hanning}; + +private: + + P _na[2]; //numerical aperature of the focusing optics + vec

f; //focal point + function apod; //apodization function + unsigned int apod_res; //resolution of apodization filter functions + + void apod_uniform() + { + apod = (P)1; + } + void apod_bartlett() + { + apod = (P)1; + apod.insert((P)1, (P)0); + } + void apod_hanning() + { + apod = (P)0; + P x, y; + for(unsigned int n=0; n k = rts::vec

(0, 0, rtsTAU), + vec

_E0 = rts::vec

(1, 0, 0), + beam_type _apod = Uniform) + : planewave

(k, _E0) + { + _na[0] = (P)0.0; + _na[1] = (P)1.0; + f = vec

( (P)0, (P)0, (P)0 ); + apod_res = 256; //set the default resolution for apodization filters + set_apod(_apod); //set the apodization function type + } + + beam

refract(rts::vec

kn) const{ + + beam

new_beam; + new_beam._na[0] = _na[0]; + new_beam._na[1] = _na[1]; + + + rts::planewave

pw = planewave

::bend(kn); + //std::cout< > mc(unsigned int N = 100000, unsigned int seed = 0) const + { + /*Create Monte-Carlo samples of a cassegrain objective by performing uniform sampling + of a sphere and projecting these samples onto an inscribed sphere. + + seed = seed for the random number generator + */ + srand(seed); //seed the random number generator + + vec

k_hat = beam::k.norm(); + + ///compute the rotation operator to transform (0, 0, 1) to k + P cos_angle = k_hat.dot(rts::vec

(0, 0, 1)); + rts::matrix rotation; + + //if the cosine of the angle is -1, the rotation is just a flip across the z axis + if(cos_angle == -1){ + rotation(2, 2) = -1; + } + else if(cos_angle != 1.0) + { + rts::vec

r_axis = rts::vec

(0, 0, 1).cross(k_hat).norm(); //compute the axis of rotation + P angle = acos(cos_angle); //compute the angle of rotation + rts::quaternion

quat; //create a quaternion describing the rotation + quat.CreateRotation(angle, r_axis); + rotation = quat.toMatrix3(); //compute the rotation matrix + } + + //find the phi values associated with the cassegrain ring + P PHI[2]; + PHI[0] = (P)asin(_na[0]); + PHI[1] = (P)asin(_na[1]); + + //calculate the z-axis cylinder coordinates associated with these angles + P Z[2]; + Z[0] = cos(PHI[0]); + Z[1] = cos(PHI[1]); + P range = Z[0] - Z[1]; + + std::vector< planewave

> samples; //create a vector of plane waves + + //draw a distribution of random phi, z values + P z, phi, theta; + for(int i=0; i spherical(1, theta, phi); //convert from spherical to cartesian coordinates + rts::vec

cart = spherical.sph2cart(); + vec

k_prime = rotation * cart; //create a sample vector + + //store a wave refracted along the given direction + //std::cout<<"k prime: "<::refract(k_prime) * apod(phi/PHI[1])); + } + + return samples; + } + + std::string str() + { + std::stringstream ss; + ss<<"Beam:"< +__global__ void gpu_planewave2efield(complex* X, complex* Y, complex* Z, unsigned int r0, unsigned int r1, + planewave w, rect q) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //get the current position + vec p = q( (T)iu/(T)r0, (T)iv/(T)r1 ); + vec r(p[0], p[1], p[2]); + + complex x( 0.0f, w.k.dot(r) ); + + if(Y == NULL) //if this is a scalar simulation + X[i] += w.E0.len() * exp(x); //use the vector magnitude as the plane wave amplitude + else + { + X[i] += w.E0[0] * exp(x); + Y[i] += w.E0[1] * exp(x); + Z[i] += w.E0[2] * exp(x); + } +} + +template +__global__ void gpu_efield_magnitude(complex* X, complex* Y, complex* Z, unsigned int r0, unsigned int r1, T* M) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + if(Y == NULL) //if this is a scalar simulation + M[i] = X[i].abs(); //compute the magnitude of the X component + else + { + M[i] = rts::vec(X[i].abs(), Y[i].abs(), Z[i].abs()).len(); + //M[i] = Z[i].abs(); + } +} + +template +__global__ void gpu_efield_real_magnitude(complex* X, complex* Y, complex* Z, unsigned int r0, unsigned int r1, T* M) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + if(Y == NULL) //if this is a scalar simulation + M[i] = X[i].abs(); //compute the magnitude of the X component + else + { + M[i] = rts::vec(X[i].real(), Y[i].real(), Z[i].real()).len(); + //M[i] = Z[i].abs(); + } +} + +template +__global__ void gpu_efield_polarization(complex* X, complex* Y, complex* Z, + unsigned int r0, unsigned int r1, + T* Px, T* Py, T* Pz) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //compute the field polarization + Px[i] = X[i].abs(); + Py[i] = Y[i].abs(); + Pz[i] = Z[i].abs(); + +} + +/* This function computes the sum of two complex fields and stores the result in *dest +*/ +template +__global__ void gpu_efield_sum(complex* dest, complex* src, unsigned int r0, unsigned int r1) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //sum the fields + dest[i] += src[i]; +} + +/* This class implements a discrete representation of an electromagnetic field + in 2D. The majority of this representation is done on the GPU. +*/ +template +class efield +{ +protected: + + bool vector; + + //gpu pointer to the field data + rts::complex* X; + rts::complex* Y; + rts::complex* Z; + + //resolution of the discrete field + unsigned int R[2]; + + //shape of the 2D field + rect pos; + + void from_planewave(planewave p) + { + unsigned int SQRT_BLOCK = 16; + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + + gpu_planewave2efield <<>> (X, Y, Z, R[0], R[1], p, pos); + } + + void init(unsigned int res0, unsigned int res1, bool _vector) + { + vector = _vector; //initialize field type + + X = Y = Z = NULL; //initialize all pointers to NULL + R[0] = res0; + R[1] = res1; + + //allocate memory on the gpu + cudaMalloc(&X, sizeof(rts::complex) * R[0] * R[1]); + cudaMemset(X, 0, sizeof(rts::complex) * R[0] * R[1]); + + if(vector) + { + cudaMalloc(&Y, sizeof(rts::complex) * R[0] * R[1]); + cudaMemset(Y, 0, sizeof(rts::complex) * R[0] * R[1]); + + cudaMalloc(&Z, sizeof(rts::complex) * R[0] * R[1]); + cudaMemset(Z, 0, sizeof(rts::complex) * R[0] * R[1]); + } + } + + void destroy() + { + if(X != NULL) cudaFree(X); + if(Y != NULL) cudaFree(Y); + if(Z != NULL) cudaFree(Z); + } + + void shallowcpy(const rts::efield & src) + { + vector = src.vector; + R[0] = src.R[0]; + R[1] = src.R[1]; + } + + void deepcpy(const rts::efield & src) + { + //perform a shallow copy + shallowcpy(src); + + //allocate memory on the gpu + if(src.X != NULL) + { + cudaMalloc(&X, sizeof(rts::complex) * R[0] * R[1]); + cudaMemcpy(X, src.X, sizeof(rts::complex) * R[0] * R[1], cudaMemcpyDeviceToDevice); + } + if(src.Y != NULL) + { + cudaMalloc(&Y, sizeof(rts::complex) * R[0] * R[1]); + cudaMemcpy(Y, src.Y, sizeof(rts::complex) * R[0] * R[1], cudaMemcpyDeviceToDevice); + } + if(src.Z != NULL) + { + cudaMalloc(&Z, sizeof(rts::complex) * R[0] * R[1]); + cudaMemcpy(Z, src.Z, sizeof(rts::complex) * R[0] * R[1], cudaMemcpyDeviceToDevice); + } + } + +public: + efield(unsigned int res0, unsigned int res1, bool _vector = true) + { + init(res0, res1, _vector); + //pos = rts::rect(rts::vec(-10, 0, -10), rts::vec(-10, 0, 10), rts::vec(10, 0, 10)); + } + + //destructor + ~efield() + { + destroy(); + } + + ///Clear the field - set all points to zero + void clear() + { + cudaMemset(X, 0, sizeof(rts::complex) * R[0] * R[1]); + + if(vector) + { + cudaMemset(Y, 0, sizeof(rts::complex) * R[0] * R[1]); + cudaMemset(Z, 0, sizeof(rts::complex) * R[0] * R[1]); + } + } + + void position(rect _p) + { + pos = _p; + } + + //access functions + complex* x(){ + return X; + } + complex* y(){ + return Y; + } + complex* z(){ + return Z; + } + unsigned int Ru(){ + return R[0]; + } + unsigned int Rv(){ + return R[1]; + } + rect p(){ + return pos; + } + + std::string str() + { + stringstream ss; + ss< & operator= (const efield & rhs) + { + destroy(); //destroy any previous information about this field + deepcpy(rhs); //create a deep copy + return *this; //return the current object + } + + //assignment operator: build an electric field from a plane wave + efield & operator= (const planewave & rhs) + { + + clear(); //clear any previous field data + from_planewave(rhs); //create a field from the planewave + return *this; //return the current object + } + + //assignment operator: add an existing electric field + efield & operator+= (const efield & rhs) + { + //if this field and the source field represent the same regions in space + if(R[0] == rhs.R[0] && R[1] == rhs.R[1] && pos == rhs.pos) + { + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //sum the fields + gpu_efield_sum <<>> (X, rhs.X, R[0], R[1]); + if(Y != NULL) + { + gpu_efield_sum <<>> (Y, rhs.Y, R[0], R[1]); + gpu_efield_sum <<>> (Z, rhs.Z, R[0], R[1]); + } + } + else + { + std::cout<<"ERROR in efield: The two summed fields do not represent the same geometry."< & operator= (const rts::beam & rhs) + { + //get a vector of monte-carlo samples + std::vector< rts::planewave > p_list = rhs.mc(); + + clear(); //clear any previous field data + for(unsigned int i = 0; i < p_list.size(); i++) + from_planewave(p_list[i]); + return *this; + } + + + //return a scalar field representing field magnitude + realfield mag() + { + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create a scalar field to store the result + realfield M(R[0], R[1]); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //compute the magnitude and store it in a scalar field + gpu_efield_magnitude <<>> (X, Y, Z, R[0], R[1], M.ptr(0)); + + return M; + } + + //return a sacalar field representing the field magnitude at an infinitely small point in time + realfield real_mag() + { + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create a scalar field to store the result + realfield M(R[0], R[1]); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((R[0] + SQRT_BLOCK -1)/SQRT_BLOCK, (R[1] + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //compute the magnitude and store it in a scalar field + gpu_efield_real_magnitude <<>> (X, Y, Z, R[0], R[1], M.ptr(0)); + + return M; + } + + //return a vector field representing field polarization + realfield polarization() + { + if(!vector) + { + std::cout<<"ERROR: Cannot compute polarization of a scalar field."< Pol(R[0], R[1]); //create a vector field to store the result + + //compute the polarization and store it in the vector field + gpu_efield_polarization <<>> (X, Y, Z, R[0], R[1], Pol.ptr(0), Pol.ptr(1), Pol.ptr(2)); + + return Pol; //return the vector field + } + + ////////FRIENDSHIP + //friend void operator<< (rts::efield &ef, rts::halfspace hs); + + +}; + + + +} //end namespace rts + +#endif diff --git a/tira/optics_old/esphere.cuh b/tira/optics_old/esphere.cuh new file mode 100644 index 0000000..1b5a559 --- /dev/null +++ b/tira/optics_old/esphere.cuh @@ -0,0 +1,162 @@ +#ifndef RTS_ESPHERE +#define RTS_ESPHERE + +#include "../math/complex.h" +#include "../math/bessel.h" +#include "../visualization/colormap.h" +#include "../optics/planewave.h" +#include "../cuda/devices.h" +#include "../optics/efield.cuh" + +namespace stim{ + +/* This class implements a discrete representation of an electromagnetic field + in 2D scattered by a sphere. This class implements Mie scattering. +*/ +template +class esphere : public efield

+{ +private: + stim::complex

n; //sphere refractive index + P a; //sphere radius + + //parameters dependent on wavelength + unsigned int Nl; //number of orders for the calculation + rts::complex

* A; //internal scattering coefficients + rts::complex

* B; //external scattering coefficients + + void calcNl(P kmag) + { + //return ceil( ((P)6.282 * a) / lambda + 4 * pow( ((P)6.282 * a) / lambda, ((P)1/(P)3)) + 2); + Nl = ceil( kmag*a + 4 * pow(kmag * a, (P)1/(P)3) + 2); + } + + void calcAB(P k, unsigned int Nl, rts::complex

* A, rts::complex

* B) + { + /* These calculations require double precision, so they are computed + using doubles and converted to P at the end. + + Input: + + k = magnitude of the k vector (tau/lambda) + Nl = highest order coefficient ([0 Nl] are computed) + */ + + //clear the previous coefficients + rts::complex

* cpuA = (rts::complex

*)malloc(sizeof(rts::complex

) * (Nl+1)); + rts::complex

* cpuB = (rts::complex

*)malloc(sizeof(rts::complex

) * (Nl+1)); + + //convert to an std complex value + complex nc = (rts::complex)n; + + //compute the magnitude of the k vector + complex kna = nc * k * (double)a; + + //compute the arguments k*a and k*n*a + complex ka(k * a, 0.0); + + //allocate space for the Bessel functions of the first and second kind (and derivatives) + unsigned int bytes = sizeof(complex) * (Nl + 1); + complex* cjv_ka = (complex*)malloc(bytes); + complex* cyv_ka = (complex*)malloc(bytes); + complex* cjvp_ka = (complex*)malloc(bytes); + complex* cyvp_ka = (complex*)malloc(bytes); + complex* cjv_kna = (complex*)malloc(bytes); + complex* cyv_kna = (complex*)malloc(bytes); + complex* cjvp_kna = (complex*)malloc(bytes); + complex* cyvp_kna = (complex*)malloc(bytes); + + //allocate space for the spherical Hankel functions and derivative + complex* chv_ka = (complex*)malloc(bytes); + complex* chvp_ka = (complex*)malloc(bytes); + + //compute the bessel functions using the CPU-based algorithm + double vm; + cbessjyva_sph(Nl, ka, vm, cjv_ka, cyv_ka, cjvp_ka, cyvp_ka); + cbessjyva_sph(Nl, kna, vm, cjv_kna, cyv_kna, cjvp_kna, cyvp_kna); + + //compute A for each order + complex i(0, 1); + complex a, b, c, d; + complex An, Bn; + for(int l=0; l<=Nl; l++) + { + //compute the Hankel functions from j and y + chv_ka[l] = cjv_ka[l] + i * cyv_ka[l]; + chvp_ka[l] = cjvp_ka[l] + i * cyvp_ka[l]; + + //Compute A (internal scattering coefficient) + //compute the numerator and denominator for A + a = cjv_ka[l] * chvp_ka[l] - cjvp_ka[l] * chv_ka[l]; + b = cjv_kna[l] * chvp_ka[l] - chv_ka[l] * cjvp_kna[l] * nc; + + //calculate A and add it to the list + rts::complex An = (2.0 * l + 1.0) * pow(i, l) * (a / b); + cpuA[l] = (rts::complex

)An; + + //Compute B (external scattering coefficient) + c = cjv_ka[l] * cjvp_kna[l] * nc - cjv_kna[l] * cjvp_ka[l]; + d = cjv_kna[l] * chvp_ka[l] - chv_ka[l] * cjvp_kna[l] * nc; + + //calculate B and add it to the list + rts::complex Bn = (2.0 * l + 1.0) * pow(i, l) * (c / d); + cpuB[l] = (rts::complex

)Bn; + + std::cout<<"A: "<) * (Nl+1)); //allocate memory for new coefficients + cudaMalloc(&B, sizeof(rts::complex

) * (Nl+1)); + + //copy the calculations from the CPU to the GPU + cudaMemcpy(A, cpuA, sizeof(rts::complex

) * (Nl+1), cudaMemcpyDeviceToHost); + cudaMemcpy(B, cpuB, sizeof(rts::complex

) * (Nl+1), cudaMemcpyDeviceToHost); + } + +public: + + esphere(unsigned int res0, unsigned int res1, P _a, rts::complex

_n, bool _scalar = false) : + efield

(res0, res1, _scalar) + { + std::cout<<"Sphere scattered field created."< & operator= (const planewave

& rhs) + { + calcNl(rhs.kmag()); //compute the number of scattering coefficients + std::cout<<"Nl: "<::str()< +__global__ void gpu_halfspace_pw2ef(complex* X, complex* Y, complex* Z, unsigned int r0, unsigned int r1, + plane P, planewave w, rect q, bool at_surface = false) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //get the current position + vec p = q( (T)iu/(T)r0, (T)iv/(T)r1 ); + + if(at_surface){ + if(P.side(p) > 0) + return; + } + else{ + if(P.side(p) >= 0) + return; + } + + //if the current position is on the wrong side of the plane + + //vec r(p[0], p[1], p[2]); + + complex x( 0.0f, w.kvec().dot(p) ); + //complex phase( 0.0f, w.phase()); + + if(Y == NULL) //if this is a scalar simulation + X[i] += w.E().len() * exp(x); //use the vector magnitude as the plane wave amplitude + else + { + //X[i] = Y[i] = Z[i] = 1; + X[i] += w.E()[0] * exp(x);// * exp(phase); + Y[i] += w.E()[1] * exp(x);// * exp(phase); + Z[i] += w.E()[2] * exp(x);// * exp(phase); + } +} + +//GPU kernel to compute the electric displacement field +template +__global__ void gpu_halfspace_pw2df(complex* X, complex* Y, complex* Z, unsigned int r0, unsigned int r1, + plane P, planewave w, rect q, T n, bool at_surface = false) +{ + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //get the current position + vec p = q( (T)iu/(T)r0, (T)iv/(T)r1 ); + + if(at_surface){ + if(P.side(p) > 0) + return; + } + else{ + if(P.side(p) >= 0) + return; + } + + //if the current position is on the wrong side of the plane + + //vec r(p[0], p[1], p[2]); + + complex x( 0.0f, w.kvec().dot(p) ); + //complex phase( 0.0f, w.phase()); + + //vec< complex > testE(1, 0, 0); + + + + if(Y == NULL) //if this is a scalar simulation + X[i] += w.E().len() * exp(x); //use the vector magnitude as the plane wave amplitude + else + { + plane< complex > cplane = plane< complex, 3>(P); + vec< complex > E_para;// = cplane.parallel(w.E()); + vec< complex > E_perp;// = cplane.perpendicular(w.E()) * (n*n); + cplane.decompose(w.E(), E_para, E_perp); + T epsilon = n*n; + + X[i] += (E_para[0] + E_perp[0] * epsilon) * exp(x); + Y[i] += (E_para[1] + E_perp[1] * epsilon) * exp(x); + Z[i] += (E_para[2] + E_perp[2] * epsilon) * exp(x); + } +} + +//computes a scalar field containing the refractive index of the half-space at each point +template +__global__ void gpu_halfspace_n(T* n, unsigned int r0, unsigned int r1, rect q, plane P, T n0, T n1){ + + int iu = blockIdx.x * blockDim.x + threadIdx.x; + int iv = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(iu >= r0 || iv >= r1) return; + + //compute the index into the field + int i = iv*r0 + iu; + + //get the current position + vec p = q( (T)iu/(T)r0, (T)iv/(T)r1 ); + + //set the appropriate refractive index + if(P.side(p) < 0) n[i] = n0; + else n[i] = n1; +} + +template +class halfspace +{ +private: + rts::plane S; //surface plane splitting the half space + rts::complex n0; //refractive index at the front of the plane + rts::complex n1; //refractive index at the back of the plane + + //lists of waves in front (pw0) and behind (pw1) the plane + std::vector< rts::planewave > w0; + std::vector< rts::planewave > w1; + + //rts::planewave pi; //incident plane wave + //rts::planewave pr; //plane wave reflected from the surface + //rts::planewave pt; //plane wave transmitted through the surface + + void init(){ + n0 = 1.0; + n1 = 1.5; + } + + //compute which side of the interface is hit by the incoming plane wave (0 = front, 1 = back) + bool facing(planewave p){ + if(p.kvec().dot(S.norm()) > 0) + return 1; + else + return 0; + } + + T calc_theta_i(vec v){ + + vec v_hat = v.norm(); + + //compute the cosine of the angle between k_hat and the plane normal + T cos_theta_i = v_hat.dot(S.norm()); + + return acos(abs(cos_theta_i)); + } + + T calc_theta_t(T ni_nt, T theta_i){ + + T sin_theta_t = ni_nt * sin(theta_i); + return asin(sin_theta_t); + } + + +public: + + //constructors + halfspace(){ + init(); + } + + halfspace(T na, T nb){ + init(); + n0 = na; + n1 = nb; + } + + halfspace(T na, T nb, plane p){ + n0 = na; + n1 = nb; + S = p; + } + + //compute the transmitted and reflective waves given the incident (vacuum) plane wave p + void incident(rts::planewave p){ + + planewave r, t; + p.scatter(S, n1.real()/n0.real(), r, t); + + //std::cout<<"i+r: "< b, unsigned int N = 10000){ + + //generate a plane wave decomposition for the beam + std::vector< planewave > pw_list = b.mc(N); + + //calculate the reflected and refracted waves for each incident wave + for(unsigned int w = 0; w < pw_list.size(); w++){ + incident(pw_list[w]); + } + } + + //return the electric field at the specified resolution and position + rts::efield E(unsigned int r0, unsigned int r1, rect R){ + + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((r0 + SQRT_BLOCK -1)/SQRT_BLOCK, (r1 + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //create an electric field + rts::efield ef(r0, r1); + ef.position(R); + + //render each plane wave + + //plane waves at the surface front + for(unsigned int w = 0; w < w0.size(); w++){ + //std::cout<<"w0 "< <<>> (ef.x(), ef.y(), ef.z(), r0, r1, S, w0[w], ef.p()); + } + + //plane waves at the surface back + for(unsigned int w = 0; w < w1.size(); w++){ + //std::cout<<"w1 "< <<>> (ef.x(), ef.y(), ef.z(), r0, r1, -S, w1[w], ef.p(), true); + } + + return ef; + } + + //return the electric displacement at the specified resolution and position + rts::efield D(unsigned int r0, unsigned int r1, rect R){ + + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((r0 + SQRT_BLOCK -1)/SQRT_BLOCK, (r1 + SQRT_BLOCK - 1)/SQRT_BLOCK); + + //create a complex vector field + rts::efield df(r0, r1); + df.position(R); + + //render each plane wave + + //plane waves at the surface front + for(unsigned int w = 0; w < w0.size(); w++){ + //std::cout<<"w0 "< <<>> (df.x(), df.y(), df.z(), r0, r1, S, w0[w], df.p(), n0.real()); + } + + //plane waves at the surface back + for(unsigned int w = 0; w < w1.size(); w++){ + //std::cout<<"w1 "< <<>> (df.x(), df.y(), df.z(), r0, r1, -S, w1[w], df.p(), n1.real(), true); + } + + return df; + } + + realfield nfield(unsigned int Ru, unsigned int Rv, rect p){ + + int maxThreads = rts::maxThreadsPerBlock(); //compute the optimal block size + int SQRT_BLOCK = (int)std::sqrt((float)maxThreads); + + //create one thread for each detector pixel + dim3 dimBlock(SQRT_BLOCK, SQRT_BLOCK); + dim3 dimGrid((Ru + SQRT_BLOCK -1)/SQRT_BLOCK, (Rv + SQRT_BLOCK - 1)/SQRT_BLOCK); + + realfield n(Ru, Rv); //create a scalar field to store the result of n + + rts::gpu_halfspace_n <<>> (n.ptr(), Ru, Rv, p, S, n0.real(), n1.real()); + + return n; + } + + std::string str(){ + std::stringstream ss; + ss<<"Half Space-------------"< (rts::efield &ef, rts::halfspace hs); + +}; + + + + +} + + +#endif diff --git a/tira/optics_old/material.h b/tira/optics_old/material.h new file mode 100644 index 0000000..f22fa0d --- /dev/null +++ b/tira/optics_old/material.h @@ -0,0 +1,153 @@ +#ifndef RTS_MATERIAL_H +#define RTS_MATERIAL_H + +#include +#include +#include +#include +#include +#include +#include +#include "../math/complex.h" +#include "../math/constants.h" +#include "../math/function.h" + +namespace stim{ + +//Material class - default representation for the material property is the refractive index (RI) +template +class material : public function< T, complex >{ + +public: + enum wave_property{microns, inverse_cm}; + enum material_property{ri, absorbance}; + +private: + + using function< T, complex >::X; + using function< T, complex >::Y; + using function< T, complex >::insert; + using function< T, complex >::bounding; + + std::string name; //name for the material (defaults to file name) + + void process_header(std::string str, wave_property& wp, material_property& mp){ + + std::stringstream ss(str); //create a stream from the data string + std::string line; + std::getline(ss, line); //get the first line as a string + while(line[0] == '#'){ //continue looping while the line is a comment + + std::stringstream lstream(line); //create a stream from the line + lstream.ignore(); //ignore the first character ('#') + + std::string prop; //get the property name + lstream>>prop; + + if(prop == "X"){ + std::string wp_name; + lstream>>wp_name; + if(wp_name == "microns") wp = microns; + else if(wp_name == "inverse_cm") wp = inverse_cm; + } + else if(prop == "Y"){ + std::string mp_name; + lstream>>mp_name; + if(mp_name == "ri") mp = ri; + else if(mp_name == "absorbance") mp = absorbance; + } + + std::getline(ss, line); //get the next line + } + + function< T, stim::complex >::process_string(str); + } + + void from_inverse_cm(){ + //convert inverse centimeters to wavelength (in microns) + for(unsigned int i=0; i(1, 0); + } + + +public: + + material(std::string filename, wave_property wp, material_property mp){ + name = filename; + load(filename, wp, mp); + } + + material(std::string filename){ + name = filename; + load(filename); + } + + material(){ + init(); + } + + complex getN(T lambda){ + return function< T, complex >::linear(lambda); + } + + void load(std::string filename, wave_property wp, material_property mp){ + + //load the file as a function + function< T, complex >::load(filename); + } + + void load(std::string filename){ + + wave_property wp = inverse_cm; + material_property mp = ri; + //turn the file into a string + std::ifstream t(filename.c_str()); //open the file as a stream + + if(!t){ + std::cout<<"ERROR: Couldn't open the material file '"<(t)), + std::istreambuf_iterator()); + + //process the header information + process_header(str, wp, mp); + + //convert units + if(wp == inverse_cm) + from_inverse_cm(); + //set the bounding values + bounding[0] = Y[0]; + bounding[1] = Y.back(); + } + std::string str(){ + std::stringstream ss; + ss< >::str(); + return ss.str(); + } + std::string get_name(){ + return name; + } + + void set_name(std::string str){ + name = str; + } + +}; + +} + + + + +#endif diff --git a/tira/optics_old/mirst-1d.cuh b/tira/optics_old/mirst-1d.cuh new file mode 100644 index 0000000..e7fde75 --- /dev/null +++ b/tira/optics_old/mirst-1d.cuh @@ -0,0 +1,447 @@ +#include "../optics/material.h" +#include "../math/complexfield.cuh" +#include "../math/constants.h" +//#include "../envi/bil.h" + +#include "cufft.h" + +#include +#include + +namespace stim{ + +//this function writes a sinc function to "dest" such that an iFFT produces a slab +template +__global__ void gpu_mirst1d_layer_fft(complex* dest, complex* ri, + T* src, T* zf, + T w, unsigned int zR, unsigned int nuR){ + //dest = complex field representing the sample + //ri = refractive indices for each wavelength + //src = intensity of the light source for each wavelength + //zf = z position of the slab interface for each wavelength (accounting for optical path length) + //w = width of the slab (in pixels) + //zR = number of z-axis samples + //nuR = number of wavelengths + + //get the current coordinate in the plane slice + int ifz = blockIdx.x * blockDim.x + threadIdx.x; + int inu = blockIdx.y * blockDim.y + threadIdx.y; + + //make sure that the thread indices are in-bounds + if(inu >= nuR || ifz >= zR) return; + + int i = inu * zR + ifz; + + T fz; + if(ifz < zR/2) + fz = ifz / (T)zR; + else + fz = -(zR - ifz) / (T)zR; + + //if the slab starts outside of the simulation domain, just return + if(zf[inu] >= zR) return; + + //fill the array along z with a sinc function representing the Fourier transform of the layer + + T opl = w * ri[inu].real(); //optical path length + + //handle the case where the slab goes outside the simulation domain + if(zf[inu] + opl >= zR) + opl = zR - zf[inu]; + + if(opl == 0) return; + + //T l = w * ri[inu].real(); + //complex e(0.0, -2 * PI * fz * (zf[inu] + zR/2 - l/2.0)); + complex e(0, -2 * stimPI * fz * (zf[inu] + opl/2)); + + complex eta = ri[inu] * ri[inu] - 1; + + //dest[i] = fz;//exp(e) * m[inu] * src[inu] * sin(PI * fz * l) / (PI * fz); + if(ifz == 0) + dest[i] += opl * exp(e) * eta * src[inu]; + else + dest[i] += opl * exp(e) * eta * src[inu] * sin(stimPI * fz * opl) / (stimPI * fz * opl); +} + +template +__global__ void gpu_mirst1d_increment_z(T* zf, complex* ri, T w, unsigned int S){ + //zf = current z depth (optical path length) in pixels + //ri = refractive index of the material + //w = actual width of the layer (in pixels) + + + //compute the index for this thread + int i = blockIdx.x * blockDim.x + threadIdx.x; + if(i >= S) return; + + if(ri == NULL) + zf[i] += w; + else + zf[i] += ri[i].real() * w; +} + +//apply the 1D MIRST filter to an existing sample (overwriting the sample) +template +__global__ void gpu_mirst1d_apply_filter(complex* sampleFFT, T* lambda, + T dFz, + T inNA, T outNA, + unsigned int lambdaR, unsigned int zR, + T sigma = 0){ + //sampleFFT = the sample in the Fourier domain (will be overwritten) + //lambda = list of wavelengths + //dFz = delta along the Fz axis in the frequency domain + //inNA = NA of the internal obscuration + //outNA = NA of the objective + //zR = number of pixels along the Fz axis (same as the z-axis) + //lambdaR = number of wavelengths + //sigma = width of the Gaussian source + int ifz = blockIdx.x * blockDim.x + threadIdx.x; + int inu = blockIdx.y * blockDim.y + threadIdx.y; + + if(inu >= lambdaR || ifz >= zR) return; + + //calculate the index into the sample FT + int i = inu * zR + ifz; + + //compute the frequency (and set all negative spatial frequencies to zero) + T fz; + if(ifz < zR / 2) + fz = ifz * dFz; + //if the spatial frequency is negative, set it to zero and exit + else{ + sampleFFT[i] = 0; + return; + } + + //compute the frequency in inverse microns + T nu = 1/lambda[inu]; + + //determine the radius of the integration circle + T nu_sq = nu * nu; + T fz_sq = (fz * fz) / 4; + + //cut off frequencies above the diffraction limit + T r; + if(fz_sq < nu_sq) + r = sqrt(nu_sq - fz_sq); + else + r = 0; + + //account for the optics + T Q = 0; + if(r > nu * inNA && r < nu * outNA) + Q = 1; + + //account for the source + //T sigma = 30.0; + T s = exp( - (r*r * sigma*sigma) / 2 ); + //T s=1; + + //compute the final filter + T mirst = 0; + if(fz != 0) + mirst = 2 * stimPI * r * s * Q * (1/fz); + + sampleFFT[i] *= mirst; + +} + +/*This object performs a 1-dimensional (layered) MIRST simulation +*/ +template +class mirst1d{ + +private: + unsigned int Z; //z-axis resolution + unsigned int pad; //pixel padding on either side of the sample + + std::vector< material > matlist; //list of materials + std::vector< T > layers; //list of layer thicknesses + + std::vector< T > lambdas; //list of wavelengths that are being simulated + unsigned int S; //number of wavelengths (size of "lambdas") + + T NA[2]; //numerical aperature (central obscuration and outer diameter) + + function source_profile; //profile (spectrum) of the source (expressed in inverse centimeters) + + complexfield scratch; //scratch GPU memory used to build samples, transforms, etc. + + void fft(int direction = CUFFT_FORWARD){ + + unsigned padZ = Z + pad; + + //create cuFFT handles + cufftHandle plan; + cufftResult result; + + if(sizeof(T) == 4) + result = cufftPlan1d(&plan, padZ, CUFFT_C2C, lambdas.size()); //single precision + else + result = cufftPlan1d(&plan, padZ, CUFFT_Z2Z, lambdas.size()); //double precision + + //check for Plan 1D errors + if(result != CUFFT_SUCCESS){ + std::cout<<"Error creating CUFFT plan for computing the FFT:"<(Z + pad , lambdas.size()); + scratch = 0; + } + + //get the list of scattering efficiency (eta) values for a specified layer + std::vector< complex > layer_etas(unsigned int l){ + + std::vector< complex > etas; + + //fill the list of etas + for(unsigned int i=0; i* gpuRi; + HANDLE_ERROR(cudaMalloc( (void**)&gpuRi, sizeof(complex) * S)); + + //allocate memory for the source profile + T* gpuSrc; + HANDLE_ERROR(cudaMalloc( (void**)&gpuSrc, sizeof(T) * S)); + + complex ri; + T source; + //store the refractive index and source profile in a CPU array + for(int inu=0; inu), cudaMemcpyHostToDevice )); + + //save the source profile to the GPU + source = source_profile(10000 / lambdas[inu]); + HANDLE_ERROR(cudaMemcpy( gpuSrc + inu, &source, sizeof(T), cudaMemcpyHostToDevice )); + + } + + //create one thread for each pixel of the field slice + dim3 gridDim, blockDim; + cuda_params(gridDim, blockDim); + stim::gpu_mirst1d_layer_fft<<>>(scratch.ptr(), gpuRi, gpuSrc, zf, wpx, paddedZ, S); + + int linBlock = stim::maxThreadsPerBlock(); //compute the optimal block size + int linGrid = S / linBlock + 1; + stim::gpu_mirst1d_increment_z <<>>(zf, gpuRi, wpx, S); + + //free memory + HANDLE_ERROR(cudaFree(gpuRi)); + HANDLE_ERROR(cudaFree(gpuSrc)); + } + + void build_sample(){ + init_scratch(); //initialize the GPU scratch space + //build_layer(1); + + T* zf; + HANDLE_ERROR(cudaMalloc(&zf, sizeof(T) * S)); + HANDLE_ERROR(cudaMemset(zf, 0, sizeof(T) * S)); + + //render each layer of the sample + for(unsigned int l=0; l>>(scratch.ptr(), gpuLambdas, + dFz, + NA[0], NA[1], + S, Zpad); + } + + //crop the image to the sample thickness - keep in mind that sample thickness != optical path length + void crop(){ + + scratch = scratch.crop(Z, S); + } + + //save the scratch field as a binary file + void to_binary(std::string filename){ + + } + + +public: + + //constructor + mirst1d(unsigned int rZ = 100, + unsigned int padding = 0){ + Z = rZ; + pad = padding; + NA[0] = 0; + NA[1] = 0.8; + S = 0; + source_profile = 1; + } + + //add a layer, thickness = microns + void add_layer(material mat, T thickness){ + matlist.push_back(mat); + layers.push_back(thickness); + } + + void add_layer(std::string filename, T thickness){ + add_layer(material(filename), thickness); + } + + //adds a profile spectrum for the light source + void set_source(std::string filename){ + source_profile.load(filename); + } + + //adds a block of wavenumbers (cm^-1) to the simulation parameters + void add_wavenumbers(unsigned int start, unsigned int stop, unsigned int step){ + unsigned int nu = start; + while(nu <= stop){ + lambdas.push_back((T)10000 / nu); + nu += step; + } + S = lambdas.size(); //increment the number of wavelengths (shorthand for later) + } + + T thickness(){ + T t = 0; + for(unsigned int l=0; l get_source(){ + return source_profile; + } + + void save_sample(std::string filename){ + //create a sample and save the magnitude as an image + build_sample(); + fft(CUFFT_INVERSE); + scratch.toImage(filename, stim::complexfield::magnitude); + } + + void save_mirst(std::string filename, bool binary = true){ + //apply the MIRST filter to a sample and save the image + + //build the sample in the Fourier domain + build_sample(); + + //apply the MIRST filter + apply_filter(); + + //apply an inverse FFT to bring the results back into the spatial domain + fft(CUFFT_INVERSE); + + crop(); + + //save the image + if(binary) + to_binary(filename); + else + scratch.toImage(filename, stim::complexfield::magnitude); + } + + + + + std::string str(){ + + stringstream ss; + ss<<"1D MIRST Simulation========================="< +#include + +#include "../math/vector.h" +#include "../math/quaternion.h" +#include "../math/constants.h" +#include "../math/plane.h" +#include "../cuda/callable.h" + +/*Basic conversions used here (assuming a vacuum) + lambda = +*/ + +namespace stim{ + namespace optics{ + +template +class planewave{ + +protected: + + vec k; //k = tau / lambda + vec< complex > E0; //amplitude + //T phi; + + CUDA_CALLABLE planewave bend(rts::vec kn) const{ + + vec kn_hat = kn.norm(); //normalize the new k + vec k_hat = k.norm(); //normalize the current k + + //std::cout<<"PLANE WAVE BENDING------------------"< new_p; //create a new plane wave + + //if kn is equal to k or -k, handle the degenerate case + T k_dot_kn = k_hat.dot(kn_hat); + + //if k . n < 0, then the bend is a reflection + //flip k_hat + if(k_dot_kn < 0) k_hat = -k_hat; + + //std::cout<<"k dot kn: "< r = k_hat.cross(kn_hat); //compute the rotation vector + + //std::cout<<"r: "< q; + q.CreateRotation(theta, r.norm()); + + //apply the rotation to E0 + vec< complex > E0n = q.toMatrix3() * E0; + + new_p.k = kn_hat * kmag(); + new_p.E0 = E0n; + + return new_p; + } + +public: + + + ///constructor: create a plane wave propagating along z, polarized along x + /*planewave(T lambda = (T)1) + { + k = rts::vec(0, 0, 1) * (TAU/lambda); + E0 = rts::vec(1, 0, 0); + }*/ + ///constructor: create a plane wave propagating along k, polarized along _E0, at frequency _omega + CUDA_CALLABLE planewave(vec kvec = rts::vec(0, 0, rtsTAU), + vec< complex > E = rts::vec(1, 0, 0), T phase = 0) + { + //phi = phase; + + k = kvec; + vec< complex > k_hat = k.norm(); + + if(E.len() == 0) //if the plane wave has an amplitude of 0 + E0 = vec(0); //just return it + else{ + vec< complex > s = (k_hat.cross(E)).norm(); //compute an orthogonal side vector + vec< complex > E_hat = (s.cross(k)).norm(); //compute a normalized E0 direction vector + E0 = E_hat * E_hat.dot(E); //compute the projection of _E0 onto E0_hat + } + + E0 = E0 * exp( complex(0, phase) ); + } + + ///multiplication operator: scale E0 + CUDA_CALLABLE planewave & operator* (const T & rhs) + { + + E0 = E0 * rhs; + return *this; + } + + CUDA_CALLABLE T lambda() const + { + return rtsTAU / k.len(); + } + + CUDA_CALLABLE T kmag() const + { + return k.len(); + } + + CUDA_CALLABLE vec< complex > E(){ + return E0; + } + + CUDA_CALLABLE vec kvec(){ + return k; + } + + /*CUDA_CALLABLE T phase(){ + return phi; + } + + CUDA_CALLABLE void phase(T p){ + phi = p; + }*/ + + CUDA_CALLABLE vec< complex > pos(vec p = vec(0, 0, 0)){ + vec< complex > result; + + T kdp = k.dot(p); + complex x = complex(0, kdp); + complex expx = exp(x); + + result[0] = E0[0] * expx; + result[1] = E0[1] * expx; + result[2] = E0[2] * expx; + + return result; + } + + //scales k based on a transition from material ni to material nt + CUDA_CALLABLE planewave n(T ni, T nt){ + return planewave(k * (nt / ni), E0); + } + + CUDA_CALLABLE planewave refract(rts::vec kn) const + { + return bend(kn); + } + + void scatter(rts::plane P, T nr, planewave &r, planewave &t){ + + int facing = P.face(k); //determine which direction the plane wave is coming in + + //if(facing == 0) //if the wave is tangent to the plane, return an identical wave + // return *this; + //else + if(facing == -1){ //if the wave hits the back of the plane, invert the plane and nr + P = P.flip(); //flip the plane + nr = 1/nr; //invert the refractive index (now nr = n0/n1) + } + + //use Snell's Law to calculate the transmitted angle + T cos_theta_i = k.norm().dot(-P.norm()); //compute the cosine of theta_i + T theta_i = acos(cos_theta_i); //compute theta_i + T sin_theta_t = (1/nr) * sin(theta_i); //compute the sine of theta_t using Snell's law + T theta_t = asin(sin_theta_t); //compute the cosine of theta_t + + bool tir = false; //flag for total internal reflection + if(theta_t != theta_t){ + tir = true; + theta_t = rtsPI / (T)2; + } + + //handle the degenerate case where theta_i is 0 (the plane wave hits head-on) + if(theta_i == 0){ + T rp = (1 - nr) / (1 + nr); //compute the Fresnel coefficients + T tp = 2 / (1 + nr); + vec kr = -k; + vec kt = k * nr; //set the k vectors for theta_i = 0 + vec< complex > Er = E0 * rp; //compute the E vectors + vec< complex > Et = E0 * tp; + T phase_t = P.p().dot(k - kt); //compute the phase offset + T phase_r = P.p().dot(k - kr); + //std::cout<<"Degeneracy: Head-On"<(kr, Er, phase_r); + t = planewave(kt, Et, phase_t); + + //std::cout<<"i + r: "< z_hat = -P.norm(); + vec y_hat = P.parallel(k).norm(); + vec x_hat = y_hat.cross(z_hat).norm(); + + //compute the k vectors for r and t + vec kr, kt; + kr = ( y_hat * sin(theta_i) - z_hat * cos(theta_i) ) * kmag(); + kt = ( y_hat * sin(theta_t) + z_hat * cos(theta_t) ) * kmag() * nr; + + //compute the magnitude of the p- and s-polarized components of the incident E vector + complex Ei_s = E0.dot(x_hat); + //int sgn = (0 < E0.dot(y_hat)) - (E0.dot(y_hat) < 0); + int sgn = E0.dot(y_hat).sgn(); + vec< complex > cx_hat = x_hat; + complex Ei_p = ( E0 - cx_hat * Ei_s ).len() * sgn; + //T Ei_p = ( E0 - x_hat * Ei_s ).len(); + //compute the magnitude of the p- and s-polarized components of the reflected E vector + complex Er_s = Ei_s * rs; + complex Er_p = Ei_p * rp; + //compute the magnitude of the p- and s-polarized components of the transmitted E vector + complex Et_s = Ei_s * ts; + complex Et_p = Ei_p * tp; + + //std::cout<<"E0: "< > Er = vec< complex >(y_hat * cos(theta_i) + z_hat * sin(theta_i)) * Er_p + cx_hat * Er_s; + //compute the transmitted E vector + vec< complex > Et = vec< complex >(y_hat * cos(theta_t) - z_hat * sin(theta_t)) * Et_p + cx_hat * Et_s; + + T phase_t = P.p().dot(k - kt); + T phase_r = P.p().dot(k - kr); + + //std::cout<<"phase r: "<(0, phase_r) ); + //r.phi = phase_r; + + //t = bend(kt); + //t.k = t.k * nr; + + t.k = kt; + t.E0 = Et * exp( complex(0, phase_t) ); + //t.phi = phase_t; + //std::cout<<"i: "< +std::ostream& operator<<(std::ostream& os, rts::planewave p) +{ + os< +#include +#include +#include +#include +#include +#include + +/**The arglist class implements command line arguments. + Example: + + 1) Create an arglist instance: + + stim::arglist args; + + 2) Add arguments: + + args.add("help", "prints this help"); + args.add("foo", "foo takes a single integer value", "", "[intval]"); + args.add("bar", "bar takes two floating point values", "", "[value1], [value2]"); + + 3) Parse the command line: + + args.parse(argc, argv); + + 4) You generally want to immediately test for help and output available arguments: + + if(args["help"].is_set()) + std::cout< desc; + + //argument values + std::vector vals; + + //integral and numerical flags for each argument + std::vector integral; + std::vector numerical; + + //range or example + std::string range; + + //flag is true when the argument is user-specified + bool flag; + + bool char_numerical(char c){ + if( (c >= '0' && c <= '9') || c == '-' || c == '.') + return true; + return false; + } + + bool char_integral(char c){ + if( (c >= '0' && c <= '9') || c == '-') + return true; + return false; + } + + /// Test if a given string contains a numerical (real) value + bool test_numerical(std::string v){ + for(size_t i = 0; i < v.length(); i++){ //for each character in the string + if(!char_numerical(v[i])) + return false; + } + return true; + } + + /// Test if a given string contains an integer value + bool test_integral(std::string v){ + for(size_t i = 0; i < v.length(); i++){ //for each character in the string + if(!char_integral(v[i])) + return false; + } + return true; + } + + void parse_val(const std::string &s){ + + vals.clear(); + + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, ' ')) { + vals.push_back(item); + } + } + + void parse_desc(const std::string &s){ + + desc.clear(); + + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, '\n')) { + desc.push_back(item); + } + } + + public: + void set_ansi(bool b){ ansi = b; } + //create an option with a given name, description, and default value + cmd_option(std::string _name, std::string _desc, std::string _default = "", std::string _range = "") + { + name = _name; + parse_desc(_desc); + parse_val(_default); + + //if a default value is provided, set the flag + if(_default != "") + flag = true; + else flag = false; + + range = _range; + + + } + + size_t nargs() + { + return vals.size(); + } + + //return the value of a text option + std::string as_string(size_t n = 0) + { + if(!flag) + { + std::cout<<"ERROR - Option requested without being set: "< n) + return vals[n]; + + else return ""; + } + + //return the value of a floating point option + double as_float(size_t n = 0) + { + if(!flag) + { + std::cout<<"ERROR - option requested without being set: "< n) + { + float r; + if ( ! (std::istringstream(vals[n]) >> r) ) r = 0; + return r; + } + + else return 0; + } + + //return the value of an integer option + int as_int(size_t n = 0) + { + if(!flag) + { + std::cout<<"ERROR - option requested without being set: "< n) + { + int r; + if ( ! (std::istringstream(vals[n]) >> r) ) r = 0; + return r; + } + + else return 0; + } + //returns true if the specified argument can be represented as a number + bool is_num(size_t n = 0){ + bool decimal = false; + for(size_t i = 0; i < vals[n].size(); i++){ + if(vals[n][i] == '-' && i != 0) return false; + else if(vals[n][i] == '.'){ + if(decimal) return false; + else decimal = true; + } + else if(vals[n][i] < '0') return false; + else if(vals[n][i] > '9') return false; + } + return true; + } + + //get the width of the left column + size_t col_width() + { + size_t n = 3; + //add the length of the option name + n += name.size(); + + //if there are any default parameters + if(vals.size() > 0) + { + //padding (parenthesis, =, etc.) + n += 6; + + //for each default option value + for(size_t v=0; v opts; + std::vector args; + + //column width of the longest option + size_t col_width; + + //list of sections + std::vector sections; + + public: + + arglist(){ + col_width = 0; + + ansi = true; + + //set ansi to false by default if this is a Windows system + // (console doesn't support ANSI colors) + #ifdef _WIN32 + ansi=false; + #endif + + } + + ///Sets ANSI support (default is on), which allows colored values in the help output. + + /// @param b is a boolean value specifying ANSI support (true is on, false is off) + void set_ansi(bool b) + { + ansi = b; + for(unsigned int i=0; i(col_width, opt.col_width()); + } + + ///Specifies a section name that can be used to organize parameters in the output. + + /// @param _name is the name of the section, which will be displayed to the user + void section(std::string _name) + { + argsection s; + s.name = _name; + s.index = opts.size(); + sections.push_back(s); + } + + ///Outputs supported arguments. If ANSI support is available, they will be color-coded. Generally this is called in response to "--help" + std::string str() + { + std::stringstream ss; + + int si = -1; + + if(sections.size() > 0) + si = 0; + + //for each option + for(unsigned int a=0; a= opts.size()){ + std::cout<<"ERROR stim::arglist: option name '"<<_name<<"' not found"<::iterator it; + it = std::find(opts.begin(), opts.end(), _name);// - opts.begin(); + + if(it == opts.end()){ + std::cout<<"ERROR - Unspecified parameter name: "<<_name<is_set(); + } + + ///Returns the number of parameters for a specified argument + + /// @param _name is the name of the argument whose parameter number will be returned + size_t nargs(std::string _name){ + std::vector::iterator it; + it = find(opts.begin(), opts.end(), _name);// - opts.begin(); + + if(it == opts.end()){ + std::cout<<"ERROR - Unspecified parameter name: "<<_name<nargs(); + } + + ///Returns the number of arguments that have been set + size_t nargs(){ + return args.size(); + } + + /// Returns the number of options that are set + size_t nopts(){ + size_t n = 0; //initialize the counter for the number of options + for(size_t i = 0; i < opts.size(); i++) //go through each option + if(opts[i].is_set()) n++; //if a value is specified, increment the counter + return n; + } + + ///Returns the name of an argument, given its index + + /// @param a is the index of the requested argument + std::string arg(size_t a){ + if (a < nargs()) { + return args[a]; + } + else { + std::cout << "stim::arguments ERROR: argument index exceeds number of available arguments" << std::endl; + exit(1); + } + } + + /// Returns an std::vector of argument strings + std::vector arg_vector(){ + return args; + } + ///Returns an object describing the argument + + /// @param _name is the name of the requested argument + cmd_option operator[](std::string _name){ + std::vector::iterator it; + it = find(opts.begin(), opts.end(), _name);// - opts.begin(); + + if(it == opts.end()){ + std::cout<<"ERROR - Unspecified parameter name: "<<_name< /* defines FILENAME_MAX */ +#include +#include +#include +#include +#include + +#include + +// set OS dependent defines, including: +// 1) path division character ('/' or '\') used for output only +// 2) command for getting the current working directory +// 3) header files for getting the current working directory +// 4) include BOOST filesystem class if compiling on GCC +#define STIM_DIV '/' +#ifdef _WIN32 + #include + #include + #define GetCurrentDir _getcwd + #define STIM_FILENAME_DIV '\\' +#else + #ifdef BOOST_PRECOMPILED + #include + #endif + #include + #define GetCurrentDir getcwd + #define STIM_FILENAME_DIV '/' + #endif + +namespace stim{ + +class filepath{ +protected: + std::string _drive; //drive on which the file is located (used for Windows) + std::vector _path; //path for the specified file (list of hierarchical directories) + + /// replaces win32 dividers with the Linux standard (used internally by default) + std::string unix_div(std::string s) { + std::replace( s.begin(), s.end(), '\\', '/'); + return s; + } + + /// gets the directory hierarchy for the current working directory + static std::string cwd(){ + char cCurrentPath[FILENAME_MAX]; + + if (!GetCurrentDir(cCurrentPath, sizeof(cCurrentPath))){ + std::cout<<"ERROR getting current working directory."< &absolute, std::vector relative){ + + std::string current = cwd(); //get the current directory as a string + + std::string current_drive; + std::vector current_dir; + parse_path(current_drive, current_dir, current); //get the current drive and directories + drive = current_drive; //all relative paths have to be relative to the current drive + if (current_dir.size() > 0) { + + // step through each directory in the relative path, adjusting the current directory + // index depending on whether the relative path is specified with '.' or '..' + int current_i = (int)current_dir.size() - 1; + int relative_i; + for (relative_i = 0; relative_i < relative.size(); relative_i++) { + if (relative[relative_i] == "..") current_i--; + else if (relative[relative_i] != ".") break; + } + if (current_i < 0) { + std::cerr << "ERROR stim::filepath - relative path is incompatible with working directory" << std::endl; + exit(1); + } + + absolute.clear(); + for (size_t i = 0; i <= current_i; i++) { + absolute.push_back(current_dir[i]); + } + for (size_t i = relative_i; i < relative.size(); i++) { + absolute.push_back(relative[i]); + } + } + else { + if (relative[0] == ".") + relative = std::vector(relative.begin() + 1, relative.end()); + absolute = relative; + } + } + + /// Parses a directory string into a drive (NULL if not Windows) and list of hierarchical directories + void parse_path(std::string &drive, std::vector &absolute, std::string dir){ + drive = ""; //initialize the drive to NULL (it will stay that way for Windows) + std::vector path; + bool relative = true; //the default path is relative + + if(dir.length() == 0) return; //return if the file locator is empty + std::string unix_dir = unix_div(dir); //replace any Windows division characters with Unix + + if(unix_dir.length() > 1 && unix_dir[1] == ':'){ //if a drive identifier is given + if(unix_dir[0] > 64 && unix_dir[0] < 91) //if the drive letter is upper case + drive = unix_dir[0] + 32; //convert it to lower case + else if(unix_dir[0] > 96 && unix_dir[0] < 123) //if the drive character is lower case + drive = unix_dir[0]; //store it as-is + else{ //otherwise throw an error + std::cerr<<"ERROR stim::filename - drive letter is invalid: "< 0) { //if there is a directory specified, remove surrounding slashes + if (unix_dir[0] == '/') { //if there is a leading slash + relative = false; //the path is not relative + unix_dir = unix_dir.substr(1, unix_dir.length() - 1); //remove the slash + } + } + if(unix_dir.size() > 0){ + if(unix_dir[unix_dir.size()-1] == '/') + unix_dir = unix_dir.substr(0, unix_dir.length() - 1); + } + + path = stim::parser::split(unix_dir, '/'); //split up the directory structure + + if(relative) + get_absolute(drive, absolute, path); + else + absolute = path; + } + + + +public: + filepath(){ + _drive = ""; + } + + filepath(const stim::filepath& p){ + *this = p; + } + + filepath(const std::string s){ + parse_path(_drive, _path, s); + } + + stim::filepath& operator=(const std::string s){ + parse_path(_drive, _path, s); //parse the string to get the drive and relative path + return *this; + } + + std::string str(){ + std::stringstream ss; + if(_drive != "") //if a drive letter is specified + ss<<_drive<<":"; //add it to the string + for(size_t i = 0; i < _path.size(); i++) + ss< get_list(){ + //this is OS dependent, so we're starting with Windows + //the Unix version requires Boost + +#ifdef _WIN32 + + HANDLE hFind = INVALID_HANDLE_VALUE; //allocate data structures for looping through files + WIN32_FIND_DATAA FindFileData; + std::vector file_list; //initialize a list to hold all matching filenames + + std::string path_string = str(); + hFind = FindFirstFileA(path_string.c_str(), &FindFileData); //find the first file that matches the specified file path + + if (hFind == INVALID_HANDLE_VALUE) { //if there are no matching files + //printf ("Invalid file handle. Error is %u.\n", GetLastError()); //print an error + return file_list; + } + else { + std::string file_name = FindFileData.cFileName; //save the file name + std::string file_path = path(); //the file is in the specified directory, so save it + stim::filename current_file(file_path + file_name); //create a stim::filename structure representing this file + if(!(FindFileData.cFileName[0] == '.' && FindFileData.cFileName[1] == '\0')) + file_list.push_back(current_file); //push the new stim::filename to the file list + + + // List all the other files in the directory. + while (FindNextFileA(hFind, &FindFileData) != 0){ //iterate until there are no more matching files + if(!(FindFileData.cFileName[0] == '.' && FindFileData.cFileName[1] == '.' && FindFileData.cFileName[2] == '\0')){ //account for the possibility of the '..' filename + file_name = FindFileData.cFileName; //save the next file + current_file = fname(file_name); //append the directory + file_list.push_back(current_file); //push it to the list + } + } + FindClose(hFind); //close the file data structure + } + return file_list; //return the list of files + +#elif BOOST_PRECOMPILED + + boost::filesystem::path p(path()); //create a path from the current filename + std::vector file_list; + if(boost::filesystem::exists(p)){ + if(boost::filesystem::is_directory(p)){ + typedef std::vector vec; // store paths, + vec v; // so we can sort them later + std::copy(boost::filesystem::directory_iterator(p), boost::filesystem::directory_iterator(), back_inserter(v)); + std::sort(v.begin(), v.end()); // sort, since directory iteration + // is not ordered on some file systems + //compare file names to the current template (look for wild cards) + for (vec::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) + { + //if the filename is a wild card *or* it matches the read file name + if( _prefix == "*" || _prefix == (*it).filename().stem().string()){ + //if the extension is a wild card *or* it matches the read file extension + if( _extension == "*" || "." + _extension == (*it).filename().extension().string()){ + file_list.push_back((*it).string()); //include it in the final list + } + + } + + + + } + + } + + } + return file_list; +#else + std::cout<<"ERROR: UNIX systems don't support file lists without the Boost precompiled libraries."< /* defines FILENAME_MAX */ +#ifdef _WIN32 + #include + #include + #define GetCurrentDir _getcwd + #define STIM_FILENAME_DIV '/' +#else + #include + #define GetCurrentDir getcwd + #define STIM_FILENAME_DIV '/' + #endif + +#include +#include +#include +#include +#include +#include +#include + +#include "../parser/parser.h" +#ifdef BOOST_PRECOMPILED +#include +#endif +namespace stim{ + +//filename class designed to work with both Windows and Unix + +class filename{ + +private: + void init(){ + + absolute = false; + + } + +protected: + + std::string drive; //drive letter (for Windows) + std::vector path; //directory hierarchy + std::string pref; //file prefix (without extension) + std::string ext; //file extension + + bool absolute; //filename is an absolute path + + //replaces win32 dividers with the Linux standard + std::string unix_div(std::string s) { + std::replace( s.begin(), s.end(), '\\', '/'); + return s; + } + + //break up a filename into a prefix and extension + void parse_name(std::string fname){ + + //find the extension + size_t xpos = fname.find_last_of('.'); //find the position of the extension period (if there is one) + if(xpos != std::string::npos){ //split the prefix and extension + pref = fname.substr(0, xpos); + ext = fname.substr(xpos + 1); + } + else + pref = fname; + } + + /// Parse a file locator into its component parts + void parse(std::string &dr, std::vector &p, std::string &pre, std::string &ex, bool &abs, std::string loc){ + dr = ""; //initialize the drive to NULL (in a Unix system, this will always be NULL) + p.clear(); //empty out the path array + pre = ""; //initialize the file prefix to NULL (if no file name is given, this will remain NULL) + ex = ""; //initialize the file extension to NULL (if no extension exists, this will remain NULL) + abs = false; //the default path is relative + + loc = unix_div(loc); //convert to unix style divisions (default representation) + + if(loc.size() == 0) return; //if the locator is NULL, just return (everything else will be NULL) + + //determine the drive (if Windows) + #ifdef _WIN32 + if(loc[1] == ':'){ //if the second character is a colon, the character right before it is the drive + abs = true; //this is an absolute path + dr = loc[0]; //copy the drive letter + loc = loc.substr(3); //remove the drive information from the locator string + } + #else + if(loc[0] == STIM_FILENAME_DIV){ + absolute = true; + loc = loc.substr(1); + } + #endif + + std::string fname = loc.substr( loc.find_last_of('/') + 1 ); //find the file name (including extension) + + //find the extension and prefix + size_t xpos = fname.find_last_of('.'); //find the position of the extension period (if there is one) + if(xpos != std::string::npos){ //split the prefix and extension + pre = fname.substr(0, xpos); + ex = fname.substr(xpos + 1); + } + else + pre = fname; + } + + //parse a file locator string + void parse(std::string loc){ + if(loc.size() == 0) return; + + loc = unix_div(loc); + + //determine the drive (if Windows) + drive = ""; + #ifdef _WIN32 + //if the second character is a colon, the character right before it is the drive + if(loc[1] == ':'){ + absolute = true; //this is an absolute path + drive = loc[0]; //copy the drive letter + loc = loc.substr(3); //remove the drive information from the locator string + } + #else + if(loc[0] == STIM_FILENAME_DIV){ + absolute = true; + loc = loc.substr(1); + } + #endif + + //determine the file name + std::string fname = loc.substr( loc.find_last_of('/') + 1 ); //find the file name (including extension) + + //parse the file name + parse_name(fname); + + //find the directory hierarchy + std::string dir = loc.substr(0, loc.find_last_of('/') + 1 ); + path = stim::parser::split(dir, '/'); + } + +public: + + filename(){ + init(); + } + + filename(std::string loc){ + init(); + parse(loc); + } + + + /// Outputs the file name (including prefix and extension) + std::string get_name(){ + if(pref == "" && ext == "") + return ""; + else + return pref + "." + ext; + } + + /// Output the file extension only (usually three characters after a '.') + std::string get_extension(){ + return ext; + } + + std::string extension(){ + return ext; + } + + void extension(std::string e){ + ext = e; + } + + /// Output the file prefix only (name before the extension) + std::string get_prefix(){ + return pref; + } + + std::string prefix(){ + return pref; + } + + void prefix(std::string p){ + pref = p; + } + + /// Output the entire path (not including the filename) + /// ex. "c:\this\is\the\directory\" + std::string dir(){ + std::stringstream ss; + + //if the path is absolute + if(absolute){ + //output the drive letter if in Windows + #ifdef _WIN32 + ss< get_list(){ + //this is OS dependent, so we're starting with Windows + //the Unix version requires Boost + +#ifdef _WIN32 + stim::filename filepath(dir_fname()); //initialize filepath with the mask + + HANDLE hFind = INVALID_HANDLE_VALUE; //allocate data structures for looping through files + WIN32_FIND_DATAA FindFileData; + std::vector file_list; //initialize a list to hold all matching filenames + + hFind = FindFirstFileA((filepath.str().c_str()), &FindFileData); //find the first file that matches the specified file path + + if (hFind == INVALID_HANDLE_VALUE) { //if there are no matching files + printf ("Invalid file handle. Error is %u.\n", GetLastError()); //print an error + } + else { + std::string file_name = FindFileData.cFileName; //save the file name + std::string file_path = dir(); //the file is in the specified directory, so save it + stim::filename current_file(file_path + file_name); //create a stim::filename structure representing this file + file_list.push_back(current_file); //push the new stim::filename to the file list + + // List all the other files in the directory. + while (FindNextFileA(hFind, &FindFileData) != 0){ //iterate until there are no more matching files + file_name = FindFileData.cFileName; //save the next file + current_file = (f_name(file_name)); //append the directory + file_list.push_back(current_file); //push it to the list + } + FindClose(hFind); //close the file data structure + } + return file_list; //return the list of files + +#elif BOOST_PRECOMPILED + + boost::filesystem::path p(dir()); //create a path from the current filename + std::vector file_list; + if(boost::filesystem::exists(p)){ + if(boost::filesystem::is_directory(p)){ + typedef std::vector vec; // store paths, + vec v; // so we can sort them later + std::copy(boost::filesystem::directory_iterator(p), boost::filesystem::directory_iterator(), back_inserter(v)); + std::sort(v.begin(), v.end()); // sort, since directory iteration + // is not ordered on some file systems + //compare file names to the current template (look for wild cards) + for (vec::const_iterator it(v.begin()), it_end(v.end()); it != it_end; ++it) + { + //if the filename is a wild card *or* it matches the read file name + if( prefix == "*" || prefix == (*it).filename().stem().string()){ + //if the extension is a wild card *or* it matches the read file extension + if( ext == "*" || "." + ext == (*it).filename().extension().string()){ + file_list.push_back((*it).string()); //include it in the final list + } + + } + + + + } + + } + + } + return file_list; +#else + std::cout<<"ERROR: UNIX systems don't support file lists without the Boost precompiled libraries."< rel_path = stim::parser::split(rel, STIM_FILENAME_DIV); + + //if the relative path doesn't contain a file, add a blank string to be used as the filename + if(rel[rel.size()-1] == STIM_FILENAME_DIV) + rel_path.push_back(""); + + //create a stack representing the current absolute path + std::stack > s(path); + + //for each token in the relative path + for(int d=0; d result_path(begin, end); + + //create a new path to be returned + stim::filename result = *this; + result.path = result_path; + result.set_name(rel_path.back()); + + return result; + } + + /// This function replaces a wildcard in the prefix with the specified string + stim::filename insert(std::string str){ + + stim::filename result = *this; //initialize the result filename to the current filename + size_t loc = result.pref.find('*'); //find a wild card in the string + if(loc == std::string::npos) //if a wildcard isn't found + result.pref += str; //append the value to the prefix + else + result.pref.replace(loc, 1, str); //replace the wildcard with the string + return result; //return the result + } + + stim::filename insert(size_t i, size_t n = 2){ + + std::stringstream ss; + ss << std::setw(n) << std::setfill('0') << i; + return insert(ss.str()); + } + + bool is_relative(){ + return !absolute; + } + + bool is_absolute(){ + return absolute; + } + + + + + + + + + +}; + + +} //end namespace stim + + +#endif + diff --git a/tira/parser/parser.h b/tira/parser/parser.h new file mode 100644 index 0000000..1a76414 --- /dev/null +++ b/tira/parser/parser.h @@ -0,0 +1,29 @@ +#ifndef STIM_PARSER_H +#define STIM_PARSER_H + +namespace stim{ + +class parser{ + +public: + + static std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; + } + + static std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; + } + +}; + +} //end namespace stim + +#endif \ No newline at end of file diff --git a/tira/parser/table.h b/tira/parser/table.h new file mode 100644 index 0000000..8d2ec0a --- /dev/null +++ b/tira/parser/table.h @@ -0,0 +1,130 @@ +#ifndef STIM_TABLE_H +#define STIM_TABLE_H + +#include +#include +#include +#include + +namespace stim{ + + class table{ + + std::vector< std::vector< std::string > > TABLE; + + size_t x; //current x and y positions for adding elements to the table + size_t y; + + void init(){ + x = y = 0; + TABLE.resize(1); //create a single row + TABLE[0].clear(); //make sure that the row is empty + } + public: + + table(){ + init(); + } + + void new_column(){ + x = 0; //reset to the first position of the column + y++; //increment the current row position + TABLE.push_back( std::vector() ); //push an empty row + } + + template + T operator<<(T i){ + std::stringstream ss; + if(std::is_fundamental()) //if the data type is numeric + ss<::digits10 + 3); //set the precision to account for all digits + ss< 0) outfile< row; + while ((pos = line.find(col_delim)) != std::string::npos) { + token = line.substr(0, pos); + row.push_back(token); + line.erase(0, pos + 1); + } + token = line.substr(0, std::string::npos); //read the last column + row.push_back(token); //push it into the table + TABLE.push_back(row); //add an empty vector to the table + } + } + + /// Returns a vector representation of the table by casting to the specified template + template + std::vector< std::vector > get_vector(){ + std::vector< std::vector > result; + result.resize(TABLE.size()); //initialize the first dimension of the returned table + for(size_t i = 0; i < TABLE.size(); i++){ //for each row + result[i].resize(TABLE[i].size()); //initialize the number of columns in this row + for(size_t j = 0; j < TABLE[i].size(); j++){ //for each column + std::stringstream ss(TABLE[i][j]); + ss >> result[i][j]; + } + } + return result; //return the casted table + } + + size_t rows(){ + return TABLE.size(); + } + + size_t cols(size_t ri){ + return TABLE[ri].size(); + } + + //returns the maximum number of columns in the table + size_t cols(){ + size_t max_c = 0; + for(size_t ri = 0; ri < rows(); ri++){ + max_c = std::max(max_c, cols(ri)); + } + return max_c; + } + + std::string operator()(size_t ri, size_t ci){ + return TABLE[ri][ci]; + } + + }; +}; + + +#endif diff --git a/tira/parser/wildcards.h b/tira/parser/wildcards.h new file mode 100644 index 0000000..7e21355 --- /dev/null +++ b/tira/parser/wildcards.h @@ -0,0 +1,82 @@ +#ifndef STIM_WILDCARDS_H +#define STIM_WILDCARDS_H + +#include +#include + + +//#include + +namespace stim{ + +class wildcards{ +public: + //generate a sequence of strings where '?' is replaced by integer increments + static std::vector increment(std::string input, + unsigned int start, + unsigned int end, + unsigned int step){ + + size_t a = input.find_first_of('?'); //find the first ? + size_t b = input.find_last_of('?'); //find the last ? + size_t n = b - a + 1; //calculate the number of ? + + std::string str_a = input.substr(0, a); //split the strings along the wildcard + std::string str_b = input.substr(b + 1, std::string::npos); + + //create a vector to hold the output strings + std::vector out; + + //create a stringstream to handle the padding + for (unsigned int i = start; i <= end; i += step){ + + std::stringstream ss; + ss << str_a + << std::setfill('0') + << std::setw(n) + << i + << str_b; + + out.push_back(ss.str()); + } + + return out; + + + } +/* + //returns a list of files that match the specified wildcards in 'd' + static std::vector get_file_list(std::string s){ + + stim::filename f(s); + std::string target_path(f.dir()); + boost::regex filter(f.name()); + + std::vector< std::string > all_matching_files; + + boost::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end + for( boost::filesystem::directory_iterator i( target_path ); i != end_itr; ++i ) + { + // Skip if not a file + if( !boost::filesystem::is_regular_file( i->status() ) ) continue; + + boost::smatch what; + + // Skip if no match + if( !boost::regex_match( i->path().filename().string(), what, filter ) ) continue; + + // File matches, store it + all_matching_files.push_back( i->path().filename().string() ); + } + + return all_matching_files; + + } +*/ + +}; + + +} //end namespace stim + +#endif diff --git a/tira/sampling/func1_from_symmetric2.h b/tira/sampling/func1_from_symmetric2.h new file mode 100644 index 0000000..779ef71 --- /dev/null +++ b/tira/sampling/func1_from_symmetric2.h @@ -0,0 +1,96 @@ +/// Reconstruct a 1D function from a 2D symmetric function. This function takes a 2D image f(x,y) as input and +/// builds a 1D function f(r) where r = sqrt(x^2 + y^2) to approximate this 2D function. +/// This is useful for several applications, such as: +/// 1) Calculating a 1D function from a noisy 2D image, when you know the 2D image is supposed to be symmetric +/// 2) Calculating the average value for every r = sqrt(x^2 + y^2) + +/// Given a set of function samples equally spaced by dx, calculate the two samples closest to x and the proximity ratio alpha. +/// This can be used to linearly interpolate between an array of equally spaced values. Given the query value x, the +/// interpolated value can be calculated as r = values[sample] * alpha + values[sample + 1] * (1 - alpha) +/// @param sample is the lowest bin closest to the query point x +/// @param alpha is the ratio of x between [sample, sample + 1] +/// @param dx is the spacing between values +/// @param x is the query point +template +void lerp_alpha(T& sample, T& alpha, T dx, T x){ + sample = std::floor(x/dx); + alpha = 1 - (x - (b * dx)) / dx; +} + +/// This function assumes that the input image is square, that the # of samples are odd, and that r=0 is at the center +/// @param fr is an array of X elements that will store the reconstructed function +/// @param dr is the spacing (in pixels) between samples in fr +template +void cpu_func1_from_symmetric2(T* fr, T& dr, T* fxy, size_t X){ + + if(X%2 == 0){ //the 2D function must be odd (a sample must be available for r=0) + std::err<<"Error, X = "< C) xii = 2 * C - xi - 1; //calculate the folded index of x + if(yi > C) yii = 2 * C - yi - 1; //calculate the folded index of y + + if(xii < yii) std::swap(xii, yii); //fold the function again along the 45-degree line + + folded[yii * C + xii] += v; //add the value to the folded function + count[yii * C + xii] += 1; //add a counter to the counter table + } + } + + //divide out the counter to correct the folded function + for(size_t i = 0; i < N){ + folded[i] /= (T)count[i]; //divide out the counter + } + + T max_r = sqrt(X * X + Y * Y); //calculate the maximum r value, which will be along the image diagonal + T dr = max_r / (X - 1); //spacing between samples in the output function f(r) + + T* fA = (T*) malloc( sizeof(T) * X); //allocate space for a counter function storing alpha weights + memset(fA, 0, sizeof(T) * X); //zero out the alpha array + memset(fr, 0, sizeof(T) * X); //zero out the output function + + T r; //register to store the value of r at each point + size_t sample; + T alpha; + for(xi = 0; xi < C; xi++){ + for(yi = 0; yi < xi; yi++){ + r = sqrt(xi*xi + yi*yi); //calculate the value of r for the current (x, y) + lerp_alpha(sample, alpha, dr, r); //calculate the lowest nearby sample index and the associated alpha weight + fr[sample] += folded[yi * C + xi] * alpha; //sum the weighted value from the folded function + fA[sample] += alpha; //sum the weight + + if(sample < X - 1){ //if we aren't dealing with the last bin + fr[sample + 1] += folded[yi * C + xi] * (1.0 - alpha); //calculate the weighted value for the second point + fA[sample + 1] += 1 - alpha; //add the second alpha value + } + } + } + + //divide out the alpha values + for(size_t i = 0; i < X; i++) + fr[i] /= fA[i]; + + //free allocated memory + free(folded); + free(count); + free(fA); +} \ No newline at end of file diff --git a/tira/structures/kdtree.cuh b/tira/structures/kdtree.cuh new file mode 100644 index 0000000..c0c751a --- /dev/null +++ b/tira/structures/kdtree.cuh @@ -0,0 +1,636 @@ +// right now the size of CUDA STACK is set to 50, increase it if you mean to make deeper tree +// data should be stored in row-major +// x1,x2,x3,x4,x5...... +// y1,y2,y3,y4,y5...... +// .................... +// .................... + +#ifndef KDTREE_H +#define KDTREE_H +#define stack_size 50 + +#include "device_launch_parameters.h" +#include +#include +#include "cuda_runtime.h" +#include +#include +#include +#include +#include +#include +#include + +namespace stim { + namespace cpu_kdtree { + template // typename refers to float or double while D refers to dimension of points + struct point { + T dim[D]; // create a structure to store every one input point + }; + + template + class cpu_kdnode { + public: + cpu_kdnode() { // constructor for initializing a kdnode + parent = NULL; // set every node's parent, left and right kdnode pointers to NULL + left = NULL; + right = NULL; + parent_idx = -1; // set parent node index to default -1 + left_idx = -1; + right_idx = -1; + split_value = -1; // set split_value to default -1 + } + int idx; // index of current node + int parent_idx, left_idx, right_idx; // index of parent, left and right nodes + cpu_kdnode *parent, *left, *right; // parent, left and right kdnodes + T split_value; // splitting value of current node + std::vector indices; // it indicates the points' indices that current node has + size_t level; // tree level of current node + }; + } // end of namespace cpu_kdtree + + template + struct cuda_kdnode { + int parent, left, right; + T split_value; + size_t num_index; // number of indices it has + int index; // the beginning index + size_t level; + }; + + template + __device__ T gpu_distance(cpu_kdtree::point &a, cpu_kdtree::point &b) { + T distance = 0; + + for (size_t i = 0; i < D; i++) { + T d = a.dim[i] - b.dim[i]; + distance += d*d; + } + return distance; + } + + template + __device__ void search_at_node(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, int cur, cpu_kdtree::point &d_query_point, size_t *d_index, T *d_distance, int *d_node) { + T best_distance = FLT_MAX; + size_t best_index = 0; + + while (true) { // break until reach the bottom + int split_axis = nodes[cur].level % D; + if (nodes[cur].left == -1) { // check whether it has left node or not + *d_node = cur; + for (int i = 0; i < nodes[cur].num_index; i++) { + size_t idx = indices[nodes[cur].index + i]; + T dist = gpu_distance(d_query_point, d_reference_points[idx]); + if (dist < best_distance) { + best_distance = dist; + best_index = idx; + } + } + break; + } + else if (d_query_point.dim[split_axis] < nodes[cur].split_value) { // jump into specific son node + cur = nodes[cur].left; + } + else { + cur = nodes[cur].right; + } + } + *d_distance = best_distance; + *d_index = best_index; + } + + template + __device__ void search_at_node_range(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point &d_query_point, int cur, T range, size_t *d_index, T *d_distance, size_t id, int *next_nodes, int *next_search_nodes, int *Judge) { + T best_distance = FLT_MAX; + size_t best_index = 0; + + int next_nodes_pos = 0; // initialize pop out order index + next_nodes[id * stack_size + next_nodes_pos] = cur; // find data that belongs to the very specific thread + next_nodes_pos++; + + while (next_nodes_pos) { + int next_search_nodes_pos = 0; // record push back order index + while (next_nodes_pos) { + cur = next_nodes[id * stack_size + next_nodes_pos - 1]; // pop out the last push in one and keep poping out + next_nodes_pos--; + int split_axis = nodes[cur].level % D; + + if (nodes[cur].left == -1) { + for (int i = 0; i < nodes[cur].num_index; i++) { + int idx = indices[nodes[cur].index + i]; // all indices are stored in one array, pick up from every node's beginning index + T d = gpu_distance(d_query_point, d_reference_points[idx]); + if (d < best_distance) { + best_distance = d; + best_index = idx; + } + } + } + else { + T d = d_query_point.dim[split_axis] - nodes[cur].split_value; + + if (fabs(d) > range) { + if (d < 0) { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].left; + next_search_nodes_pos++; + } + else { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].right; + next_search_nodes_pos++; + } + } + else { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].right; + next_search_nodes_pos++; + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].left; + next_search_nodes_pos++; + if (next_search_nodes_pos > stack_size) { + printf("Thread conflict might be caused by thread %d, so please try smaller input max_tree_levels\n", id); + (*Judge)++; + } + } + } + } + for (int i = 0; i < next_search_nodes_pos; i++) + next_nodes[id * stack_size + i] = next_search_nodes[id * stack_size + i]; + next_nodes_pos = next_search_nodes_pos; + } + *d_distance = best_distance; + *d_index = best_index; + } + + template + __device__ void search(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point &d_query_point, size_t *d_index, T *d_distance, size_t id, int *next_nodes, int *next_search_nodes, int *Judge) { + int best_node = 0; + T best_distance = FLT_MAX; + size_t best_index = 0; + T radius = 0; + + search_at_node(nodes, indices, d_reference_points, 0, d_query_point, &best_index, &best_distance, &best_node); + radius = sqrt(best_distance); // get range + int cur = best_node; + + while (nodes[cur].parent != -1) { + int parent = nodes[cur].parent; + int split_axis = nodes[parent].level % D; + + T tmp_dist = FLT_MAX; + size_t tmp_idx; + if (fabs(nodes[parent].split_value - d_query_point.dim[split_axis]) <= radius) { + if (nodes[parent].left != cur) + search_at_node_range(nodes, indices, d_reference_points, d_query_point, nodes[parent].left, radius, &tmp_idx, &tmp_dist, id, next_nodes, next_search_nodes, Judge); + else + search_at_node_range(nodes, indices, d_reference_points, d_query_point, nodes[parent].right, radius, &tmp_idx, &tmp_dist, id, next_nodes, next_search_nodes, Judge); + } + if (tmp_dist < best_distance) { + best_distance = tmp_dist; + best_index = tmp_idx; + } + cur = parent; + } + *d_distance = sqrt(best_distance); + *d_index = best_index; + } + + template + __global__ void search_batch(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point *d_query_points, size_t d_query_count, size_t *d_indices, T *d_distances, int *next_nodes, int *next_search_nodes, int *Judge) { + size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= d_query_count) return; // avoid segfault + + search(nodes, indices, d_reference_points, d_query_points[idx], &d_indices[idx], &d_distances[idx], idx, next_nodes, next_search_nodes, Judge); // every query points are independent + } + + template + void search_stream(cuda_kdnode *d_nodes, size_t *d_index, cpu_kdtree::point *d_reference_points, cpu_kdtree::point *query_stream_points, size_t stream_count, size_t *indices, T *distances) { + unsigned int threads = (unsigned int)(stream_count > 1024 ? 1024 : stream_count); + unsigned int blocks = (unsigned int)(stream_count / threads + (stream_count % threads ? 1 : 0)); + + cpu_kdtree::point *d_query_points; + size_t *d_indices; + T *d_distances; + + int *next_nodes; + int *next_search_nodes; + + HANDLE_ERROR(cudaMalloc((void**)&d_query_points, sizeof(T) * stream_count * D)); + HANDLE_ERROR(cudaMalloc((void**)&d_indices, sizeof(size_t) * stream_count)); + HANDLE_ERROR(cudaMalloc((void**)&d_distances, sizeof(T) * stream_count)); + HANDLE_ERROR(cudaMalloc((void**)&next_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMalloc((void**)&next_search_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMemcpy(d_query_points, query_stream_points, sizeof(T) * stream_count * D, cudaMemcpyHostToDevice)); + + int *Judge = NULL; + + search_batch<<>> (d_nodes, d_index, d_reference_points, d_query_points, stream_count, d_indices, d_distances, next_nodes, next_search_nodes, Judge); + + if(Judge == NULL) { + HANDLE_ERROR(cudaMemcpy(indices, d_indices, sizeof(size_t) * stream_count, cudaMemcpyDeviceToHost)); + HANDLE_ERROR(cudaMemcpy(distances, d_distances, sizeof(T) * stream_count, cudaMemcpyDeviceToHost)); + } + + HANDLE_ERROR(cudaFree(next_nodes)); + HANDLE_ERROR(cudaFree(next_search_nodes)); + HANDLE_ERROR(cudaFree(d_query_points)); + HANDLE_ERROR(cudaFree(d_indices)); + HANDLE_ERROR(cudaFree(d_distances)); + } + + template // set dimension of data to default 3 + class kdtree { + protected: + int current_axis; // current judging axis + int n_id; // store the total number of nodes + std::vector < typename cpu_kdtree::point > *tmp_points; // transfer or temperary points + std::vector < typename cpu_kdtree::point > cpu_tmp_points; // for cpu searching + cpu_kdtree::cpu_kdnode *root; // root node + static kdtree *cur_tree_ptr; + #ifdef __CUDACC__ + cuda_kdnode *d_nodes; + size_t *d_index; + cpu_kdtree::point* d_reference_points; + size_t npts; + int num_nodes; + #endif + public: + kdtree() { // constructor for creating a cpu_kdtree + cur_tree_ptr = this; // create a class pointer points to the current class value + n_id = 0; // set total number of points to default 0 + } + + ~kdtree() { // destructor of cpu_kdtree + std::vector *> next_nodes; + next_nodes.push_back(root); + while (next_nodes.size()) { + std::vector *> next_search_nodes; + while (next_nodes.size()) { + cpu_kdtree::cpu_kdnode *cur = next_nodes.back(); + next_nodes.pop_back(); + if (cur->left) + next_search_nodes.push_back(cur->left); + if (cur->right) + next_search_nodes.push_back(cur->right); + delete cur; + } + next_nodes = next_search_nodes; + } + root = NULL; + #ifdef __CUDACC__ + HANDLE_ERROR(cudaFree(d_nodes)); + HANDLE_ERROR(cudaFree(d_index)); + HANDLE_ERROR(cudaFree(d_reference_points)); + #endif + } + + void cpu_create(std::vector < typename cpu_kdtree::point > &reference_points, size_t max_levels) { + tmp_points = &reference_points; + root = new cpu_kdtree::cpu_kdnode(); // initializing the root node + root->idx = n_id++; // the index of root is 0 + root->level = 0; // tree level begins at 0 + root->indices.resize(reference_points.size()); // get the number of points + for (size_t i = 0; i < reference_points.size(); i++) { + root->indices[i] = i; // set indices of input points + } + std::vector *> next_nodes; // next nodes + next_nodes.push_back(root); // push back the root node + while (next_nodes.size()) { + std::vector *> next_search_nodes; // next search nodes + while (next_nodes.size()) { // two same WHILE is because we need to make a new vector to store nodes for search + cpu_kdtree::cpu_kdnode *current_node = next_nodes.back(); // handle node one by one (right first) + next_nodes.pop_back(); // pop out current node in order to store next round of nodes + if (current_node->level < max_levels) { + if (current_node->indices.size() > 1) { // split if the nonleaf node contains more than one point + cpu_kdtree::cpu_kdnode *left = new cpu_kdtree::cpu_kdnode(); + cpu_kdtree::cpu_kdnode *right = new cpu_kdtree::cpu_kdnode(); + left->idx = n_id++; // set the index of current node's left node + right->idx = n_id++; + split(current_node, left, right); // split left and right and determine a node + std::vector temp; // empty vecters of int + //temp.resize(current_node->indices.size()); + current_node->indices.swap(temp); // clean up current node's indices + current_node->left = left; + current_node->right = right; + current_node->left_idx = left->idx; + current_node->right_idx = right->idx; + if (right->indices.size()) + next_search_nodes.push_back(right); // left pop out first + if (left->indices.size()) + next_search_nodes.push_back(left); + } + } + } + next_nodes = next_search_nodes; // go deeper within the tree + } + } + + static bool sort_points(const size_t a, const size_t b) { // create functor for std::sort + std::vector < typename cpu_kdtree::point > &pts = *cur_tree_ptr->tmp_points; // put cur_tree_ptr to current input points' pointer + return pts[a].dim[cur_tree_ptr->current_axis] < pts[b].dim[cur_tree_ptr->current_axis]; + } + + void split(cpu_kdtree::cpu_kdnode *cur, cpu_kdtree::cpu_kdnode *left, cpu_kdtree::cpu_kdnode *right) { + std::vector < typename cpu_kdtree::point > &pts = *tmp_points; + current_axis = cur->level % D; // indicate the judicative dimension or axis + std::sort(cur->indices.begin(), cur->indices.end(), sort_points); // using SortPoints as comparison function to sort the data + size_t mid_value = cur->indices[cur->indices.size() / 2]; // odd in the mid_value, even take the floor + cur->split_value = pts[mid_value].dim[current_axis]; // get the parent node + left->parent = cur; // set the parent of the next search nodes to current node + right->parent = cur; + left->level = cur->level + 1; // level + 1 + right->level = cur->level + 1; + left->parent_idx = cur->idx; // set its parent node's index + right->parent_idx = cur->idx; + for (size_t i = 0; i < cur->indices.size(); i++) { // split into left and right half-space one by one + size_t idx = cur->indices[i]; + if (pts[idx].dim[current_axis] < cur->split_value) + left->indices.push_back(idx); + else + right->indices.push_back(idx); + } + } + + /// create a KD-tree given a pointer to an array of reference points and the number of reference points + /// @param h_reference_points is a host array containing the reference points in (x0, y0, z0, ...., ) order + /// @param reference_count is the number of reference point in the array + /// @param max_levels is the deepest number of tree levels allowed + void create(T *h_reference_points, size_t reference_count, size_t max_levels) { + #ifdef __CUDACC__ + if (max_levels > 10) { + std::cout<<"The max_tree_levels should be smaller!"<(bb, h_reference_points, reference_count); + + std::vector < typename cpu_kdtree::point > reference_points(reference_count); // restore the reference points in particular way + for (size_t j = 0; j < reference_count; j++) + for (size_t i = 0; i < D; i++) + reference_points[j].dim[i] = h_reference_points[j * D + i]; // creating a tree on cpu + (*this).cpu_create(reference_points, max_levels); // building a tree on cpu + cpu_kdtree::cpu_kdnode *d_root = (*this).get_root(); + num_nodes = (*this).get_num_nodes(); + npts = reference_count; // also equals to reference_count + + HANDLE_ERROR(cudaMalloc((void**)&d_nodes, sizeof(cuda_kdnode) * num_nodes)); // copy data from host to device + HANDLE_ERROR(cudaMalloc((void**)&d_index, sizeof(size_t) * npts)); + HANDLE_ERROR(cudaMalloc((void**)&d_reference_points, sizeof(cpu_kdtree::point) * npts)); + + std::vector < cuda_kdnode > tmp_nodes(num_nodes); + std::vector indices(npts); + std::vector *> next_nodes; + size_t cur_pos = 0; + next_nodes.push_back(d_root); + while (next_nodes.size()) { + std::vector *> next_search_nodes; + while (next_nodes.size()) { + cpu_kdtree::cpu_kdnode *cur = next_nodes.back(); + next_nodes.pop_back(); + int id = cur->idx; // the nodes at same level are independent + tmp_nodes[id].level = cur->level; + tmp_nodes[id].parent = cur->parent_idx; + tmp_nodes[id].left = cur->left_idx; + tmp_nodes[id].right = cur->right_idx; + tmp_nodes[id].split_value = cur->split_value; + tmp_nodes[id].num_index = cur->indices.size(); // number of index + if (cur->indices.size()) { + for (size_t i = 0; i < cur->indices.size(); i++) + indices[cur_pos + i] = cur->indices[i]; + + tmp_nodes[id].index = (int)cur_pos; // beginning index of reference_points that every bottom node has + cur_pos += cur->indices.size(); // store indices continuously for every query_point + } + else { + tmp_nodes[id].index = -1; + } + + if (cur->left) + next_search_nodes.push_back(cur->left); + + if (cur->right) + next_search_nodes.push_back(cur->right); + } + next_nodes = next_search_nodes; + } + HANDLE_ERROR(cudaMemcpy(d_nodes, &tmp_nodes[0], sizeof(cuda_kdnode) * tmp_nodes.size(), cudaMemcpyHostToDevice)); + HANDLE_ERROR(cudaMemcpy(d_index, &indices[0], sizeof(size_t) * indices.size(), cudaMemcpyHostToDevice)); + HANDLE_ERROR(cudaMemcpy(d_reference_points, &reference_points[0], sizeof(cpu_kdtree::point) * reference_count, cudaMemcpyHostToDevice)); + + #else + std::vector < typename cpu_kdtree::point > reference_points(reference_count); // restore the reference points in particular way + for (size_t j = 0; j < reference_count; j++) + for (size_t i = 0; i < D; i++) + reference_points[j].dim[i] = h_reference_points[j * D + i]; + cpu_create(reference_points, max_levels); + cpu_tmp_points = *tmp_points; + + #endif + } + + int get_num_nodes() const { // get the total number of nodes + return n_id; + } + + cpu_kdtree::cpu_kdnode* get_root() const { // get the root node of tree + return root; + } + + T cpu_distance(const cpu_kdtree::point &a, const cpu_kdtree::point &b) { + T distance = 0; + + for (size_t i = 0; i < D; i++) { + T d = a.dim[i] - b.dim[i]; + distance += d*d; + } + return distance; + } + + void cpu_search_at_node(cpu_kdtree::cpu_kdnode *cur, const cpu_kdtree::point &query, size_t *index, T *distance, cpu_kdtree::cpu_kdnode **node) { + T best_distance = FLT_MAX; // initialize the best distance to max of floating point + size_t best_index = 0; + std::vector < typename cpu_kdtree::point > pts = cpu_tmp_points; + while (true) { + size_t split_axis = cur->level % D; + if (cur->left == NULL) { // risky but acceptable, same goes for right because left and right are in same pace + *node = cur; // pointer points to a pointer + for (size_t i = 0; i < cur->indices.size(); i++) { + size_t idx = cur->indices[i]; + T d = cpu_distance(query, pts[idx]); // compute distances + /// if we want to compute k nearest neighbor, we can input the last resul + /// (last_best_dist < dist < best_dist) to select the next point until reaching to k + if (d < best_distance) { + best_distance = d; + best_index = idx; // record the nearest neighbor index + } + } + break; // find the target point then break the loop + } + else if (query.dim[split_axis] < cur->split_value) { // if it has son node, visit the next node on either left side or right side + cur = cur->left; + } + else { + cur = cur->right; + } + } + *index = best_index; + *distance = best_distance; + } + + void cpu_search_at_node_range(cpu_kdtree::cpu_kdnode *cur, const cpu_kdtree::point &query, T range, size_t *index, T *distance) { + T best_distance = FLT_MAX; // initialize the best distance to max of floating point + size_t best_index = 0; + std::vector < typename cpu_kdtree::point > pts = cpu_tmp_points; + std::vector < typename cpu_kdtree::cpu_kdnode*> next_node; + next_node.push_back(cur); + while (next_node.size()) { + std::vector*> next_search; + while (next_node.size()) { + cur = next_node.back(); + next_node.pop_back(); + size_t split_axis = cur->level % D; + if (cur->left == NULL) { + for (size_t i = 0; i < cur->indices.size(); i++) { + size_t idx = cur->indices[i]; + T d = cpu_distance(query, pts[idx]); + if (d < best_distance) { + best_distance = d; + best_index = idx; + } + } + } + else { + T d = query.dim[split_axis] - cur->split_value; // computer distance along specific axis or dimension + /// there are three possibilities: on either left or right, and on both left and right + if (fabs(d) > range) { // absolute value of floating point to see if distance will be larger that best_dist + if (d < 0) + next_search.push_back(cur->left); // every left[split_axis] is less and equal to cur->split_value, so it is possible to find the nearest point in this region + else + next_search.push_back(cur->right); + } + else { // it is possible that nereast neighbor will appear on both left and right + next_search.push_back(cur->left); + next_search.push_back(cur->right); + } + } + } + next_node = next_search; // pop out at least one time + } + *index = best_index; + *distance = best_distance; + } + + void cpu_search(T *h_query_points, size_t query_count, size_t *h_indices, T *h_distances) { + /// first convert the input query point into specific type + cpu_kdtree::point query; + for (size_t j = 0; j < query_count; j++) { + for (size_t i = 0; i < D; i++) + query.dim[i] = h_query_points[j * D + i]; + /// find the nearest node, this will be the upper bound for the next time searching + cpu_kdtree::cpu_kdnode *best_node = NULL; + T best_distance = FLT_MAX; + size_t best_index = 0; + T radius = 0; // radius for range + cpu_search_at_node(root, query, &best_index, &best_distance, &best_node); // simple search to rougly determine a result for next search step + radius = sqrt(best_distance); // It is possible that nearest will appear in another region + /// find other possibilities + cpu_kdtree::cpu_kdnode *cur = best_node; + while (cur->parent != NULL) { // every node that you pass will be possible to be the best node + /// go up + cpu_kdtree::cpu_kdnode *parent = cur->parent; // travel back to every node that we pass through + size_t split_axis = (parent->level) % D; + /// search other nodes + size_t tmp_index; + T tmp_distance = FLT_MAX; + if (fabs(parent->split_value - query.dim[split_axis]) <= radius) { + /// search opposite node + if (parent->left != cur) + cpu_search_at_node_range(parent->left, query, radius, &tmp_index, &tmp_distance); // to see whether it is its mother node's left son node + else + cpu_search_at_node_range(parent->right, query, radius, &tmp_index, &tmp_distance); + } + if (tmp_distance < best_distance) { + best_distance = tmp_distance; + best_index = tmp_index; + } + cur = parent; + } + h_indices[j] = best_index; + h_distances[j] = best_distance; + } + } + + /// search the KD tree for nearest neighbors to a set of specified query points + /// @param h_query_points an array of query points in (x0, y0, z0, ...) order + /// @param query_count is the number of query points + /// @param indices are the indices to the nearest reference point for each query points + /// @param distances is an array containing the distance between each query point and the nearest reference point + void search(T *h_query_points, size_t query_count, size_t *indices, T *distances) { + #ifdef __CUDACC__ + std::vector < typename cpu_kdtree::point > query_points(query_count); + for (size_t j = 0; j < query_count; j++) + for (size_t i = 0; i < D; i++) + query_points[j].dim[i] = h_query_points[j * D + i]; + + cudaDeviceProp prop; + cudaGetDeviceProperties(&prop, 0); + + size_t query_memory = D * sizeof(T) * query_count; + size_t N = 3 * query_memory / prop.totalGlobalMem; //consider index and distance, roughly 3 times + if (N > 1) { + N++; + size_t stream_count = query_count / N; + for (size_t n = 0; n < N; n++) { + size_t query_stream_start = n * stream_count; + search_stream(d_nodes, d_index, d_reference_points, &query_points[query_stream_start], stream_count, &indices[query_stream_start], &distances[query_stream_start]); + } + size_t stream_remain_count = query_count - N * stream_count; + if (stream_remain_count > 0) { + size_t query_remain_start = N * stream_count; + search_stream(d_nodes, d_index, d_reference_points, &query_points[query_remain_start], stream_remain_count, &indices[query_remain_start], &distances[query_remain_start]); + } + } + else { + unsigned int threads = (unsigned int)(query_count > 1024 ? 1024 : query_count); + unsigned int blocks = (unsigned int)(query_count / threads + (query_count % threads ? 1 : 0)); + + cpu_kdtree::point *d_query_points; // create a pointer pointing to query points on gpu + size_t *d_indices; + T *d_distances; + + int *next_nodes; // create two STACK-like array + int *next_search_nodes; + + int *Judge = NULL; // judge variable to see whether one thread is overwrite another thread's memory + + HANDLE_ERROR(cudaMalloc((void**)&d_query_points, sizeof(T) * query_count * D)); + HANDLE_ERROR(cudaMalloc((void**)&d_indices, sizeof(size_t) * query_count)); + HANDLE_ERROR(cudaMalloc((void**)&d_distances, sizeof(T) * query_count)); + HANDLE_ERROR(cudaMalloc((void**)&next_nodes, threads * blocks * stack_size * sizeof(int))); // STACK size right now is 50, you can change it if you mean to + HANDLE_ERROR(cudaMalloc((void**)&next_search_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMemcpy(d_query_points, &query_points[0], sizeof(T) * query_count * D, cudaMemcpyHostToDevice)); + + search_batch<<>> (d_nodes, d_index, d_reference_points, d_query_points, query_count, d_indices, d_distances, next_nodes, next_search_nodes, Judge); + + if (Judge == NULL) { // do the following work if the thread works safely + HANDLE_ERROR(cudaMemcpy(indices, d_indices, sizeof(size_t) * query_count, cudaMemcpyDeviceToHost)); + HANDLE_ERROR(cudaMemcpy(distances, d_distances, sizeof(T) * query_count, cudaMemcpyDeviceToHost)); + } + + HANDLE_ERROR(cudaFree(next_nodes)); + HANDLE_ERROR(cudaFree(next_search_nodes)); + HANDLE_ERROR(cudaFree(d_query_points)); + HANDLE_ERROR(cudaFree(d_indices)); + HANDLE_ERROR(cudaFree(d_distances)); + } + + #else + cpu_search(h_query_points, query_count, indices, distances); + + #endif + + } + + }; //end class kdtree + + template + kdtree* kdtree::cur_tree_ptr = NULL; // definition of cur_tree_ptr pointer points to the current class + +} //end namespace stim +#endif \ No newline at end of file diff --git a/tira/structures/kdtree_dep.cuh b/tira/structures/kdtree_dep.cuh new file mode 100644 index 0000000..093c71a --- /dev/null +++ b/tira/structures/kdtree_dep.cuh @@ -0,0 +1,627 @@ +// right now the size of CUDA STACK is set to 50, increase it if you mean to make deeper tree +// data should be stored in row-major +// x1,x2,x3,x4,x5...... +// y1,y2,y3,y4,y5...... +// .................... +// .................... + +#ifndef KDTREE_H +#define KDTREE_H +#define stack_size 50 + +#include "device_launch_parameters.h" +#include +#include +#include "cuda_runtime.h" +#include +#include +#include +#include +#include +#include +#include + +namespace stim { + namespace cpu_kdtree { + template // typename refers to float or double while D refers to dimension of points + struct point { + T dim[D]; // create a structure to store every one input point + }; + + template + class cpu_kdnode { + public: + cpu_kdnode() { // constructor for initializing a kdnode + parent = NULL; // set every node's parent, left and right kdnode pointers to NULL + left = NULL; + right = NULL; + parent_idx = -1; // set parent node index to default -1 + left_idx = -1; + right_idx = -1; + split_value = -1; // set split_value to default -1 + } + int idx; // index of current node + int parent_idx, left_idx, right_idx; // index of parent, left and right nodes + cpu_kdnode *parent, *left, *right; // parent, left and right kdnodes + T split_value; // splitting value of current node + std::vector indices; // it indicates the points' indices that current node has + size_t level; // tree level of current node + }; + } // end of namespace cpu_kdtree + + template + struct cuda_kdnode { + int parent, left, right; + T split_value; + size_t num_index; // number of indices it has + int index; // the beginning index + size_t level; + }; + + template + __device__ T gpu_distance(cpu_kdtree::point &a, cpu_kdtree::point &b) { + T distance = 0; + + for (size_t i = 0; i < D; i++) { + T d = a.dim[i] - b.dim[i]; + distance += d*d; + } + return distance; + } + + template + __device__ void search_at_node(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, int cur, cpu_kdtree::point &d_query_point, size_t *d_index, T *d_distance, int *d_node) { + T best_distance = FLT_MAX; + size_t best_index = 0; + + while (true) { // break until reach the bottom + int split_axis = nodes[cur].level % D; + if (nodes[cur].left == -1) { // check whether it has left node or not + *d_node = cur; + for (int i = 0; i < nodes[cur].num_index; i++) { + size_t idx = indices[nodes[cur].index + i]; + T dist = gpu_distance(d_query_point, d_reference_points[idx]); + if (dist < best_distance) { + best_distance = dist; + best_index = idx; + } + } + break; + } + else if (d_query_point.dim[split_axis] < nodes[cur].split_value) { // jump into specific son node + cur = nodes[cur].left; + } + else { + cur = nodes[cur].right; + } + } + *d_distance = best_distance; + *d_index = best_index; + } + + template + __device__ void search_at_node_range(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point &d_query_point, int cur, T range, size_t *d_index, T *d_distance, size_t id, int *next_nodes, int *next_search_nodes, int *Judge) { + T best_distance = FLT_MAX; + size_t best_index = 0; + + int next_nodes_pos = 0; // initialize pop out order index + next_nodes[id * stack_size + next_nodes_pos] = cur; // find data that belongs to the very specific thread + next_nodes_pos++; + + while (next_nodes_pos) { + int next_search_nodes_pos = 0; // record push back order index + while (next_nodes_pos) { + cur = next_nodes[id * stack_size + next_nodes_pos - 1]; // pop out the last push in one and keep poping out + next_nodes_pos--; + int split_axis = nodes[cur].level % D; + + if (nodes[cur].left == -1) { + for (int i = 0; i < nodes[cur].num_index; i++) { + int idx = indices[nodes[cur].index + i]; // all indices are stored in one array, pick up from every node's beginning index + T d = gpu_distance(d_query_point, d_reference_points[idx]); + if (d < best_distance) { + best_distance = d; + best_index = idx; + } + } + } + else { + T d = d_query_point.dim[split_axis] - nodes[cur].split_value; + + if (fabs(d) > range) { + if (d < 0) { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].left; + next_search_nodes_pos++; + } + else { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].right; + next_search_nodes_pos++; + } + } + else { + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].right; + next_search_nodes_pos++; + next_search_nodes[id * stack_size + next_search_nodes_pos] = nodes[cur].left; + next_search_nodes_pos++; + if (next_search_nodes_pos > stack_size) { + printf("Thread conflict might be caused by thread %d, so please try smaller input max_tree_levels\n", id); + (*Judge)++; + } + } + } + } + for (int i = 0; i < next_search_nodes_pos; i++) + next_nodes[id * stack_size + i] = next_search_nodes[id * stack_size + i]; + next_nodes_pos = next_search_nodes_pos; + } + *d_distance = best_distance; + *d_index = best_index; + } + + template + __device__ void search(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point &d_query_point, size_t *d_index, T *d_distance, size_t id, int *next_nodes, int *next_search_nodes, int *Judge) { + int best_node = 0; + T best_distance = FLT_MAX; + size_t best_index = 0; + T radius = 0; + + search_at_node(nodes, indices, d_reference_points, 0, d_query_point, &best_index, &best_distance, &best_node); + radius = sqrt(best_distance); // get range + int cur = best_node; + + while (nodes[cur].parent != -1) { + int parent = nodes[cur].parent; + int split_axis = nodes[parent].level % D; + + T tmp_dist = FLT_MAX; + size_t tmp_idx; + if (fabs(nodes[parent].split_value - d_query_point.dim[split_axis]) <= radius) { + if (nodes[parent].left != cur) + search_at_node_range(nodes, indices, d_reference_points, d_query_point, nodes[parent].left, radius, &tmp_idx, &tmp_dist, id, next_nodes, next_search_nodes, Judge); + else + search_at_node_range(nodes, indices, d_reference_points, d_query_point, nodes[parent].right, radius, &tmp_idx, &tmp_dist, id, next_nodes, next_search_nodes, Judge); + } + if (tmp_dist < best_distance) { + best_distance = tmp_dist; + best_index = tmp_idx; + } + cur = parent; + } + *d_distance = sqrt(best_distance); + *d_index = best_index; + } + + template + __global__ void search_batch(cuda_kdnode *nodes, size_t *indices, cpu_kdtree::point *d_reference_points, cpu_kdtree::point *d_query_points, size_t d_query_count, size_t *d_indices, T *d_distances, int *next_nodes, int *next_search_nodes, int *Judge) { + size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= d_query_count) return; // avoid segfault + + search(nodes, indices, d_reference_points, d_query_points[idx], &d_indices[idx], &d_distances[idx], idx, next_nodes, next_search_nodes, Judge); // every query points are independent + } + + template + void search_stream(cuda_kdnode *d_nodes, size_t *d_index, cpu_kdtree::point *d_reference_points, cpu_kdtree::point *query_stream_points, size_t stream_count, size_t *indices, T *distances) { + unsigned int threads = (unsigned int)(stream_count > 1024 ? 1024 : stream_count); + unsigned int blocks = (unsigned int)(stream_count / threads + (stream_count % threads ? 1 : 0)); + + cpu_kdtree::point *d_query_points; + size_t *d_indices; + T *d_distances; + + int *next_nodes; + int *next_search_nodes; + + HANDLE_ERROR(cudaMalloc((void**)&d_query_points, sizeof(T) * stream_count * D)); + HANDLE_ERROR(cudaMalloc((void**)&d_indices, sizeof(size_t) * stream_count)); + HANDLE_ERROR(cudaMalloc((void**)&d_distances, sizeof(T) * stream_count)); + HANDLE_ERROR(cudaMalloc((void**)&next_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMalloc((void**)&next_search_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMemcpy(d_query_points, query_stream_points, sizeof(T) * stream_count * D, cudaMemcpyHostToDevice)); + + int *Judge = NULL; + + search_batch<<>> (d_nodes, d_index, d_reference_points, d_query_points, stream_count, d_indices, d_distances, next_nodes, next_search_nodes, Judge); + + if(Judge == NULL) { + HANDLE_ERROR(cudaMemcpy(indices, d_indices, sizeof(size_t) * stream_count, cudaMemcpyDeviceToHost)); + HANDLE_ERROR(cudaMemcpy(distances, d_distances, sizeof(T) * stream_count, cudaMemcpyDeviceToHost)); + } + + HANDLE_ERROR(cudaFree(next_nodes)); + HANDLE_ERROR(cudaFree(next_search_nodes)); + HANDLE_ERROR(cudaFree(d_query_points)); + HANDLE_ERROR(cudaFree(d_indices)); + HANDLE_ERROR(cudaFree(d_distances)); + } + + template // set dimension of data to default 3 + class kdtree { + protected: + int current_axis; // current judging axis + int n_id; // store the total number of nodes + std::vector < typename cpu_kdtree::point > *tmp_points; // transfer or temperary points + std::vector < typename cpu_kdtree::point > cpu_tmp_points; // for cpu searching + cpu_kdtree::cpu_kdnode *root; // root node + static kdtree *cur_tree_ptr; + #ifdef __CUDACC__ + cuda_kdnode *d_nodes; + size_t *d_index; + cpu_kdtree::point* d_reference_points; + size_t npts; + int num_nodes; + #endif + public: + kdtree() { // constructor for creating a cpu_kdtree + cur_tree_ptr = this; // create a class pointer points to the current class value + n_id = 0; // set total number of points to default 0 + } + + ~kdtree() { // destructor of cpu_kdtree + std::vector *> next_nodes; + next_nodes.push_back(root); + while (next_nodes.size()) { + std::vector *> next_search_nodes; + while (next_nodes.size()) { + cpu_kdtree::cpu_kdnode *cur = next_nodes.back(); + next_nodes.pop_back(); + if (cur->left) + next_search_nodes.push_back(cur->left); + if (cur->right) + next_search_nodes.push_back(cur->right); + delete cur; + } + next_nodes = next_search_nodes; + } + root = NULL; + #ifdef __CUDACC__ + HANDLE_ERROR(cudaFree(d_nodes)); + HANDLE_ERROR(cudaFree(d_index)); + HANDLE_ERROR(cudaFree(d_reference_points)); + #endif + } + + void cpu_create(std::vector < typename cpu_kdtree::point > &reference_points, size_t max_levels) { + tmp_points = &reference_points; + root = new cpu_kdtree::cpu_kdnode(); // initializing the root node + root->idx = n_id++; // the index of root is 0 + root->level = 0; // tree level begins at 0 + root->indices.resize(reference_points.size()); // get the number of points + for (size_t i = 0; i < reference_points.size(); i++) { + root->indices[i] = i; // set indices of input points + } + std::vector *> next_nodes; // next nodes + next_nodes.push_back(root); // push back the root node + while (next_nodes.size()) { + std::vector *> next_search_nodes; // next search nodes + while (next_nodes.size()) { // two same WHILE is because we need to make a new vector to store nodes for search + cpu_kdtree::cpu_kdnode *current_node = next_nodes.back(); // handle node one by one (right first) + next_nodes.pop_back(); // pop out current node in order to store next round of nodes + if (current_node->level < max_levels) { + if (current_node->indices.size() > 1) { // split if the nonleaf node contains more than one point + cpu_kdtree::cpu_kdnode *left = new cpu_kdtree::cpu_kdnode(); + cpu_kdtree::cpu_kdnode *right = new cpu_kdtree::cpu_kdnode(); + left->idx = n_id++; // set the index of current node's left node + right->idx = n_id++; + split(current_node, left, right); // split left and right and determine a node + std::vector temp; // empty vecters of int + //temp.resize(current_node->indices.size()); + current_node->indices.swap(temp); // clean up current node's indices + current_node->left = left; + current_node->right = right; + current_node->left_idx = left->idx; + current_node->right_idx = right->idx; + if (right->indices.size()) + next_search_nodes.push_back(right); // left pop out first + if (left->indices.size()) + next_search_nodes.push_back(left); + } + } + } + next_nodes = next_search_nodes; // go deeper within the tree + } + } + + static bool sort_points(const size_t a, const size_t b) { // create functor for std::sort + std::vector < typename cpu_kdtree::point > &pts = *cur_tree_ptr->tmp_points; // put cur_tree_ptr to current input points' pointer + return pts[a].dim[cur_tree_ptr->current_axis] < pts[b].dim[cur_tree_ptr->current_axis]; + } + + void split(cpu_kdtree::cpu_kdnode *cur, cpu_kdtree::cpu_kdnode *left, cpu_kdtree::cpu_kdnode *right) { + std::vector < typename cpu_kdtree::point > &pts = *tmp_points; + current_axis = cur->level % D; // indicate the judicative dimension or axis + std::sort(cur->indices.begin(), cur->indices.end(), sort_points); // using SortPoints as comparison function to sort the data + size_t mid_value = cur->indices[cur->indices.size() / 2]; // odd in the mid_value, even take the floor + cur->split_value = pts[mid_value].dim[current_axis]; // get the parent node + left->parent = cur; // set the parent of the next search nodes to current node + right->parent = cur; + left->level = cur->level + 1; // level + 1 + right->level = cur->level + 1; + left->parent_idx = cur->idx; // set its parent node's index + right->parent_idx = cur->idx; + for (size_t i = 0; i < cur->indices.size(); i++) { // split into left and right half-space one by one + size_t idx = cur->indices[i]; + if (pts[idx].dim[current_axis] < cur->split_value) + left->indices.push_back(idx); + else + right->indices.push_back(idx); + } + } + + void create(T *h_reference_points, size_t reference_count, size_t max_levels) { + #ifdef __CUDACC__ + if (max_levels > 10) { + std::cout<<"The max_tree_levels should be smaller!"<(bb, h_reference_points, reference_count); + + std::vector < typename cpu_kdtree::point> reference_points(reference_count); // restore the reference points in particular way + for (size_t j = 0; j < reference_count; j++) + for (size_t i = 0; i < D; i++) + reference_points[j].dim[i] = h_reference_points[j * D + i]; // creating a tree on cpu + (*this).cpu_create(reference_points, max_levels); // building a tree on cpu + cpu_kdtree::cpu_kdnode *d_root = (*this).get_root(); + num_nodes = (*this).get_num_nodes(); + npts = reference_count; // also equals to reference_count + + HANDLE_ERROR(cudaMalloc((void**)&d_nodes, sizeof(cuda_kdnode) * num_nodes)); // copy data from host to device + HANDLE_ERROR(cudaMalloc((void**)&d_index, sizeof(size_t) * npts)); + HANDLE_ERROR(cudaMalloc((void**)&d_reference_points, sizeof(cpu_kdtree::point) * npts)); + + std::vector < cuda_kdnode > tmp_nodes(num_nodes); + std::vector indices(npts); + std::vector *> next_nodes; + size_t cur_pos = 0; + next_nodes.push_back(d_root); + while (next_nodes.size()) { + std::vector *> next_search_nodes; + while (next_nodes.size()) { + cpu_kdtree::cpu_kdnode *cur = next_nodes.back(); + next_nodes.pop_back(); + int id = cur->idx; // the nodes at same level are independent + tmp_nodes[id].level = cur->level; + tmp_nodes[id].parent = cur->parent_idx; + tmp_nodes[id].left = cur->left_idx; + tmp_nodes[id].right = cur->right_idx; + tmp_nodes[id].split_value = cur->split_value; + tmp_nodes[id].num_index = cur->indices.size(); // number of index + if (cur->indices.size()) { + for (size_t i = 0; i < cur->indices.size(); i++) + indices[cur_pos + i] = cur->indices[i]; + + tmp_nodes[id].index = (int)cur_pos; // beginning index of reference_points that every bottom node has + cur_pos += cur->indices.size(); // store indices continuously for every query_point + } + else { + tmp_nodes[id].index = -1; + } + + if (cur->left) + next_search_nodes.push_back(cur->left); + + if (cur->right) + next_search_nodes.push_back(cur->right); + } + next_nodes = next_search_nodes; + } + HANDLE_ERROR(cudaMemcpy(d_nodes, &tmp_nodes[0], sizeof(cuda_kdnode) * tmp_nodes.size(), cudaMemcpyHostToDevice)); + HANDLE_ERROR(cudaMemcpy(d_index, &indices[0], sizeof(size_t) * indices.size(), cudaMemcpyHostToDevice)); + HANDLE_ERROR(cudaMemcpy(d_reference_points, &reference_points[0], sizeof(cpu_kdtree::point) * reference_count, cudaMemcpyHostToDevice)); + + #else + std::vector < typename cpu_kdtree::point > reference_points(reference_count); // restore the reference points in particular way + for (size_t j = 0; j < reference_count; j++) + for (size_t i = 0; i < D; i++) + reference_points[j].dim[i] = h_reference_points[j * D + i]; + cpu_create(reference_points, max_levels); + cpu_tmp_points = *tmp_points; + + #endif + } + + int get_num_nodes() const { // get the total number of nodes + return n_id; + } + + cpu_kdtree::cpu_kdnode* get_root() const { // get the root node of tree + return root; + } + + T cpu_distance(const cpu_kdtree::point &a, const cpu_kdtree::point &b) { + T distance = 0; + + for (size_t i = 0; i < D; i++) { + T d = a.dim[i] - b.dim[i]; + distance += d*d; + } + return distance; + } + + void cpu_search_at_node(cpu_kdtree::cpu_kdnode *cur, const cpu_kdtree::point &query, size_t *index, T *distance, cpu_kdtree::cpu_kdnode **node) { + T best_distance = FLT_MAX; // initialize the best distance to max of floating point + size_t best_index = 0; + std::vector < typename cpu_kdtree::point > pts = cpu_tmp_points; + while (true) { + size_t split_axis = cur->level % D; + if (cur->left == NULL) { // risky but acceptable, same goes for right because left and right are in same pace + *node = cur; // pointer points to a pointer + for (size_t i = 0; i < cur->indices.size(); i++) { + size_t idx = cur->indices[i]; + T d = cpu_distance(query, pts[idx]); // compute distances + /// if we want to compute k nearest neighbor, we can input the last resul + /// (last_best_dist < dist < best_dist) to select the next point until reaching to k + if (d < best_distance) { + best_distance = d; + best_index = idx; // record the nearest neighbor index + } + } + break; // find the target point then break the loop + } + else if (query.dim[split_axis] < cur->split_value) { // if it has son node, visit the next node on either left side or right side + cur = cur->left; + } + else { + cur = cur->right; + } + } + *index = best_index; + *distance = best_distance; + } + + void cpu_search_at_node_range(cpu_kdtree::cpu_kdnode *cur, const cpu_kdtree::point &query, T range, size_t *index, T *distance) { + T best_distance = FLT_MAX; // initialize the best distance to max of floating point + size_t best_index = 0; + std::vector < typename cpu_kdtree::point > pts = cpu_tmp_points; + std::vector < typename cpu_kdtree::cpu_kdnode*> next_node; + next_node.push_back(cur); + while (next_node.size()) { + std::vector*> next_search; + while (next_node.size()) { + cur = next_node.back(); + next_node.pop_back(); + size_t split_axis = cur->level % D; + if (cur->left == NULL) { + for (size_t i = 0; i < cur->indices.size(); i++) { + size_t idx = cur->indices[i]; + T d = cpu_distance(query, pts[idx]); + if (d < best_distance) { + best_distance = d; + best_index = idx; + } + } + } + else { + T d = query.dim[split_axis] - cur->split_value; // computer distance along specific axis or dimension + /// there are three possibilities: on either left or right, and on both left and right + if (fabs(d) > range) { // absolute value of floating point to see if distance will be larger that best_dist + if (d < 0) + next_search.push_back(cur->left); // every left[split_axis] is less and equal to cur->split_value, so it is possible to find the nearest point in this region + else + next_search.push_back(cur->right); + } + else { // it is possible that nereast neighbor will appear on both left and right + next_search.push_back(cur->left); + next_search.push_back(cur->right); + } + } + } + next_node = next_search; // pop out at least one time + } + *index = best_index; + *distance = best_distance; + } + + void cpu_search(T *h_query_points, size_t query_count, size_t *h_indices, T *h_distances) { + /// first convert the input query point into specific type + cpu_kdtree::point query; + for (size_t j = 0; j < query_count; j++) { + for (size_t i = 0; i < D; i++) + query.dim[i] = h_query_points[j * D + i]; + /// find the nearest node, this will be the upper bound for the next time searching + cpu_kdtree::cpu_kdnode *best_node = NULL; + T best_distance = FLT_MAX; + size_t best_index = 0; + T radius = 0; // radius for range + cpu_search_at_node(root, query, &best_index, &best_distance, &best_node); // simple search to rougly determine a result for next search step + radius = sqrt(best_distance); // It is possible that nearest will appear in another region + /// find other possibilities + cpu_kdtree::cpu_kdnode *cur = best_node; + while (cur->parent != NULL) { // every node that you pass will be possible to be the best node + /// go up + cpu_kdtree::cpu_kdnode *parent = cur->parent; // travel back to every node that we pass through + size_t split_axis = (parent->level) % D; + /// search other nodes + size_t tmp_index; + T tmp_distance = FLT_MAX; + if (fabs(parent->split_value - query.dim[split_axis]) <= radius) { + /// search opposite node + if (parent->left != cur) + cpu_search_at_node_range(parent->left, query, radius, &tmp_index, &tmp_distance); // to see whether it is its mother node's left son node + else + cpu_search_at_node_range(parent->right, query, radius, &tmp_index, &tmp_distance); + } + if (tmp_distance < best_distance) { + best_distance = tmp_distance; + best_index = tmp_index; + } + cur = parent; + } + h_indices[j] = best_index; + h_distances[j] = best_distance; + } + } + + void search(T *h_query_points, size_t query_count, size_t *indices, T *distances) { + #ifdef __CUDACC__ + std::vector < typename cpu_kdtree::point > query_points(query_count); + for (size_t j = 0; j < query_count; j++) + for (size_t i = 0; i < D; i++) + query_points[j].dim[i] = h_query_points[j * D + i]; + + cudaDeviceProp prop; + cudaGetDeviceProperties(&prop, 0); + + size_t query_memory = D * sizeof(T) * query_count; + size_t N = 3 * query_memory / prop.totalGlobalMem; //consider index and distance, roughly 3 times + if (N > 1) { + N++; + size_t stream_count = query_count / N; + for (size_t n = 0; n < N; n++) { + size_t query_stream_start = n * stream_count; + search_stream(d_nodes, d_index, d_reference_points, &query_points[query_stream_start], stream_count, &indices[query_stream_start], &distances[query_stream_start]); + } + size_t stream_remain_count = query_count - N * stream_count; + if (stream_remain_count > 0) { + size_t query_remain_start = N * stream_count; + search_stream(d_nodes, d_index, d_reference_points, &query_points[query_remain_start], stream_remain_count, &indices[query_remain_start], &distances[query_remain_start]); + } + } + else { + unsigned int threads = (unsigned int)(query_count > 1024 ? 1024 : query_count); + unsigned int blocks = (unsigned int)(query_count / threads + (query_count % threads ? 1 : 0)); + + cpu_kdtree::point *d_query_points; // create a pointer pointing to query points on gpu + size_t *d_indices; + T *d_distances; + + int *next_nodes; // create two STACK-like array + int *next_search_nodes; + + int *Judge = NULL; // judge variable to see whether one thread is overwrite another thread's memory + + HANDLE_ERROR(cudaMalloc((void**)&d_query_points, sizeof(T) * query_count * D)); + HANDLE_ERROR(cudaMalloc((void**)&d_indices, sizeof(size_t) * query_count)); + HANDLE_ERROR(cudaMalloc((void**)&d_distances, sizeof(T) * query_count)); + HANDLE_ERROR(cudaMalloc((void**)&next_nodes, threads * blocks * stack_size * sizeof(int))); // STACK size right now is 50, you can change it if you mean to + HANDLE_ERROR(cudaMalloc((void**)&next_search_nodes, threads * blocks * stack_size * sizeof(int))); + HANDLE_ERROR(cudaMemcpy(d_query_points, &query_points[0], sizeof(T) * query_count * D, cudaMemcpyHostToDevice)); + + search_batch<<>> (d_nodes, d_index, d_reference_points, d_query_points, query_count, d_indices, d_distances, next_nodes, next_search_nodes, Judge); + + if (Judge == NULL) { // do the following work if the thread works safely + HANDLE_ERROR(cudaMemcpy(indices, d_indices, sizeof(size_t) * query_count, cudaMemcpyDeviceToHost)); + HANDLE_ERROR(cudaMemcpy(distances, d_distances, sizeof(T) * query_count, cudaMemcpyDeviceToHost)); + } + + HANDLE_ERROR(cudaFree(next_nodes)); + HANDLE_ERROR(cudaFree(next_search_nodes)); + HANDLE_ERROR(cudaFree(d_query_points)); + HANDLE_ERROR(cudaFree(d_indices)); + HANDLE_ERROR(cudaFree(d_distances)); + } + + #else + cpu_search(h_query_points, query_count, indices, distances); + + #endif + + } + + }; //end class kdtree + + template + kdtree* kdtree::cur_tree_ptr = NULL; // definition of cur_tree_ptr pointer points to the current class + +} //end namespace stim +#endif \ No newline at end of file diff --git a/tira/ui/progressbar.h b/tira/ui/progressbar.h new file mode 100644 index 0000000..9d92805 --- /dev/null +++ b/tira/ui/progressbar.h @@ -0,0 +1,65 @@ +#ifndef RTS_PROGRESSBAR_H +#define RTS_PROGRESSBAR_H + +#include +#include +#include +using namespace std; + +void stimPrintTime(unsigned long long s) { + std::cout << std::fixed << std::showpoint << std::setprecision(1); + if (s >= 60) { //if there are more than 60 seconds + unsigned long long m = s / 60; //get the number of minutes + s = s - m * 60; //get the number of remaining seconds + if (m > 60) { //if more than 60 minutes + unsigned long long h = m / 60; + m = m - h * 60; //get the number of remaining minutes + if (h > 24) { //if there are more than 24 hours + unsigned long long d = h / 24; + h = h - d * 24; //get the number of remaining hours + std::cout << " " << d << "d"; + } + std::cout << " " << h << "h"; + } + std::cout << " " << m << "m"; + } + std::cout << " " << s << "s"; +} + +static void rtsProgressBar(unsigned int percent, double elapsed_s = 0) +{ + //std::cout< (float)i/(float)40 *100) + bar << "*"; + else + bar << " "; + } + bar<<"]"; + cout << "\r"; // carriage return back to beginning of line + cout << bar.str() << " " << slash[x] << " " << percent << " %"; // print the bars and percentage + if (elapsed_s > 0) { + stimPrintTime((unsigned long long)elapsed_s); + if (percent > 0) { + std::cout << " / "; + double s_per_percent = elapsed_s / percent; + double total = s_per_percent * 100; + stimPrintTime((unsigned long long)total); + } + } + cout.flush(); + x++; // increment to make the slash appear to rotate + if(x == 4) + x = 0; // reset slash animation +} + +#endif diff --git a/tira/util/filesize.h b/tira/util/filesize.h new file mode 100644 index 0000000..c053f71 --- /dev/null +++ b/tira/util/filesize.h @@ -0,0 +1,34 @@ +#ifndef STIM_UTIL_FILESIZE_H +#define STIM_UTIL_FILESIZE_H + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +namespace stim{ +static size_t file_size(std::string filename){ +#ifdef _WIN32 + HANDLE hFile = CreateFile(filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(hFile == INVALID_HANDLE_VALUE) return 0; + LARGE_INTEGER size; + if(!GetFileSizeEx(hFile, &size)){ + CloseHandle(hFile); + return 0; + } + CloseHandle(hFile); + return (size_t)size.QuadPart; +#else + struct stat sb; + stat(filename.c_str(), &sb); + return sb.st_size; +#endif +} + +} //end namespace stim + + + +#endif diff --git a/tira/visualization/aabb2.h b/tira/visualization/aabb2.h new file mode 100644 index 0000000..fe38f74 --- /dev/null +++ b/tira/visualization/aabb2.h @@ -0,0 +1,49 @@ +#ifndef STIM_AABB2_H +#define STIM_AABB2_H + +#include + +namespace stim{ + +/// Structure for a 2D axis aligned bounding box +template +struct aabb2{ + +//protected: + + T low[2]; //top left corner position + T high[2]; //dimensions along x and y + +//public: + + CUDA_CALLABLE aabb2(T x, T y){ //initialize an axis aligned bounding box of size 0 at the given position + low[0] = high[0] = x; //set the position to the user specified coordinates + low[1] = high[1] = y; + } + + //insert a point into the bounding box, growing the box appropriately + CUDA_CALLABLE void insert(T x, T y){ + if(x < low[0]) low[0] = x; + if(y < low[1]) low[1] = y; + + if(x > high[0]) high[0] = x; + if(y > high[1]) high[1] = y; + } + + //trim the bounding box so that the lower bounds are (x, y) + CUDA_CALLABLE void trim_low(T x, T y){ + if(low[0] < x) low[0] = x; + if(low[1] < y) low[1] = y; + } + + CUDA_CALLABLE void trim_high(T x, T y){ + if(high[0] > x) high[0] = x; + if(high[1] > y) high[1] = y; + } + +}; + +} + + +#endif \ No newline at end of file diff --git a/tira/visualization/aabb3.h b/tira/visualization/aabb3.h new file mode 100644 index 0000000..84acb37 --- /dev/null +++ b/tira/visualization/aabb3.h @@ -0,0 +1,62 @@ +#ifndef STIM_AABB3_H +#define STIM_AABB3_H + +#include +#include + +namespace stim{ + + //template + //using aabb3 = aabbn; +/// Structure for a 3D axis aligned bounding box +template +struct aabb3 : public aabbn{ + + CUDA_CALLABLE aabb3() : aabbn() {} + CUDA_CALLABLE aabb3(T x0, T y0, T z0, T x1, T y1, T z1){ + low[0] = x0; + low[1] = y0; + low[2] = z0; + high[0] = x0; + high[1] = x1; + high[2] = x2; + } + + CUDA_CALLABLE aabb3(T x, T y, T z) { + low[0] = high[0] = x; + low[1] = high[1] = y; + low[2] = high[2] = z; + } + + CUDA_CALLABLE void insert(T x, T y, T z) { + T p[3]; + p[0] = x; + p[1] = y; + p[2] = z; + aabbn::insert(p); + } + + CUDA_CALLABLE void trim_low(T x, T y, T z) { + T p[3]; + p[0] = x; + p[1] = y; + p[2] = z; + aabbn::trim_low(p); + } + + CUDA_CALLABLE void trim_high(T x, T y, T z) { + T p[3]; + p[0] = x; + p[1] = y; + p[2] = z; + aabbn::trim_high(p); + } + + + +}; + +} + + +#endif \ No newline at end of file diff --git a/tira/visualization/aabbn.h b/tira/visualization/aabbn.h new file mode 100644 index 0000000..48e0f5b --- /dev/null +++ b/tira/visualization/aabbn.h @@ -0,0 +1,129 @@ +#ifndef STIM_AABBN_H +#define STIM_AABBN_H + +#include +#include + +namespace stim{ + +/// Structure for a 3D axis aligned bounding box +template +struct aabbn{ + +//protected: + + T low[D]; //top left corner position + T high[D]; //dimensions along x and y and z + + CUDA_CALLABLE void init(T* i) { + for (size_t d = 0; d < D; d++) + low[d] = high[d] = i[d]; + } + + CUDA_CALLABLE aabbn() {} + CUDA_CALLABLE aabbn(T* i) { + init(i); + } + + /// For even inputs to the constructor, the input could be one point or a set of pairs of points + CUDA_CALLABLE aabbn(T x0, T x1) { + if (D == 1) { + low[0] = x0; + high[0] = x1; + } + else if (D == 2) { + low[0] = high[0] = x0; + low[1] = high[1] = x1; + } + } + + /// In the case of 3 inputs, this must be a 3D bounding box, so initialize to a box of size 0 at (x, y, z) + /*CUDA_CALLABLE aabbn(T x, T y, T z) { + low[0] = high[0] = x; + low[1] = high[1] = y; + low[2] = high[2] = z; + }*/ + + CUDA_CALLABLE aabbn(T x0, T y0, T x1, T y1) { + if (D == 2) { + low[0] = x0; + high[0] = x1; + low[1] = y0; + high[1] = y1; + } + else if(D == 4){ + low[0] = high[0] = x0; + low[1] = high[1] = y0; + low[2] = high[2] = x1; + low[3] = high[3] = y1; + } + } + + /*CUDA_CALLABLE aabbn(T x0, T y0, T z0, T x1, T y1, T z1) { + if (D == 3) { + low[0] = x0; + high[0] = x1; + low[1] = y0; + high[1] = y1; + low[2] = z0; + high[2] = z1; + } + else if (D == 6) { + low[0] = high[0] = x0; + low[1] = high[1] = y0; + low[2] = high[2] = z0; + low[3] = high[3] = x1; + low[4] = high[4] = y1; + low[5] = high[5] = z1; + } + }*/ + + + //insert a point into the bounding box, growing the box appropriately + CUDA_CALLABLE void insert(T* p){ + for(size_t d = 0; d < D; d++){ + if(p[d] < low[d]) low[d] = p[d]; + if(p[d] > high[d]) high[d] = p[d]; + } + } + + //trim the bounding box so that the lower bounds are b(x, y, z, ...) + CUDA_CALLABLE void trim_low(T* b){ + for(size_t d = 0; d < D; d++) + if(low[d] < b[d]) low[d] = b[d]; + } + + CUDA_CALLABLE void trim_high(T* b){ + for(size_t d = 0; d < D; d++) + if(high[d] > b[d]) high[d] = b[d]; + } + + CUDA_CALLABLE T length(size_t d) { + return high[d] - low[d]; + } + + CUDA_CALLABLE aabbn operator*(T s) { + aabbn newbox; + for (size_t d = 0; d < D; d++) { + T c = (low[d] + high[d]) / 2; + T l = high[d] - low[d]; + newbox.low[d] = c - l * s / 2; + newbox.high[d] = c + l * s / 2; + } + return newbox; + } + + //translate the box along dimension d a distance of v + CUDA_CALLABLE void translate(size_t d, T v) { + for (size_t d = 0; d < D; d++) { + low[d] += v; + high[d] += v; + } + } + +}; + +} + + +#endif \ No newline at end of file diff --git a/tira/visualization/aaboundingbox.h b/tira/visualization/aaboundingbox.h new file mode 100644 index 0000000..554c140 --- /dev/null +++ b/tira/visualization/aaboundingbox.h @@ -0,0 +1,101 @@ +#ifndef STIM_AABB +#define STIM_AABB + +namespace stim{ + + +/// This class describes a structure for an axis-aligned bounding box +template< typename T > +class aaboundingbox{ + +public: + bool set; //has the bounding box been set to include any points? + stim::vec3 A; //minimum point in the bounding box + stim::vec3 B; //maximum point in the bounding box + + aaboundingbox(){ //constructor generates an empty bounding box + set = false; + } + + + /// Test if a point is inside of the bounding box and returns true if it is. + + /// @param p is the point to be tested + bool test(stim::vec3 p){ + + for(unsigned d = 0; d < p.size(); p++){ //for each dimension + if(p[d] < A[d]) return false; //if the point is less than the minimum bound, return false + if(p[d] > B[d]) return false; //if the point is greater than the max bound, return false + } + return true; + } + + /// Expand the bounding box to include the specified point. + + /// @param p is the point to be included + void expand(stim::vec3 p){ + + if(!set){ //if the bounding box is empty, fill it with the current point + A = B = p; + set = true; + } + + for(unsigned d = 0; d < p.size(); d++){ //for each dimension + if(p[d] < A[d]) A[d] = p[d]; //expand the bounding box as necessary + if(p[d] > B[d]) B[d] = p[d]; + } + } + + /// Return the center point of the bounding box as a stim::vec + stim::vec3 center(){ + return (B + A) * 0.5; + } + + /// Return the size of the bounding box as a stim::vec + stim::vec3 size(){ + return (B - A); + } + + /// Generate a string for the bounding box + std::string str(){ + std::stringstream ss; + ss<"< > + resample(unsigned int n) + { + stim::vec3 step_size = (B-A)/n; + std::vector > boundingboxes(n*n*n); + for(int x = 0; x < n; x++) + { + for(int y = 0; y < n; y++) + { + for(int z = 0; z < n; z++) + { + ///create the bounding box + stim::aaboundingbox box; + box.A[0] = A[0]+step_size[0]*x; + box.A[1] = A[1]+step_size[1]*y; + box.A[2] = A[2]+step_size[2]*z; + + box.B[0] = A[0]+step_size[0]*(x+1); + box.B[1] = A[1]+step_size[1]*(y+1); + box.B[2] = A[2]+step_size[2]*(z+1); + ///put the new bounding box into the right place + boundingboxes[x*n*n + y*n + z] = box; + } + } + } + return boundingboxes; + } + + +}; //end stim::aabb + + +}; //end namespace stim + +#endif diff --git a/tira/visualization/camera.h b/tira/visualization/camera.h new file mode 100644 index 0000000..269b2a0 --- /dev/null +++ b/tira/visualization/camera.h @@ -0,0 +1,208 @@ +#include +#include +#include + +#include +#include + +#ifndef STIM_CAMERA_H +#define STIM_CAMERA_H + +namespace stim{ + +class camera +{ + vec3 d; //direction that the camera is pointing + vec3 p; //position of the camera + vec3 up; //"up" direction + float focus; //focal length of the camera + float fov; + + //private function makes sure that the up vector is orthogonal to the direction vector and both are normalized + void stabalize() + { + vec3 side = up.cross(d); + up = d.cross(side); + up = up.norm(); + d = d.norm(); + } + +public: + void setPosition(vec3 pos) + { + p = pos; + } + void setPosition(float x, float y, float z){setPosition(vec3(x, y, z));} + + void setFocalDistance(float distance){focus = distance;} + void setFOV(float field_of_view){fov = field_of_view;} + + void LookAt(vec3 pos) + { + //find the new direction + d = pos - p; + + //find the distance from the look-at point to the current position + focus = d.len(); + + //stabalize the camera + stabalize(); + } + void LookAt(float px, float py, float pz){LookAt(vec3(px, py, pz));} + void LookAt(vec3 pos, vec3 new_up){up = new_up; LookAt(pos);} + void LookAt(float px, float py, float pz, float ux, float uy, float uz){LookAt(vec3(px, py, pz), vec3(ux, uy, uz));} + void LookAtDolly(float lx, float ly, float lz) + { + //find the current focus point + vec3 f = p + focus*d; + vec3 T = vec3(lx, ly, lz) - f; + p = p + T; + } + + void Dolly(vec3 direction) + { + p = p+direction; + } + void Dolly(float x, float y, float z){Dolly(vec3(x, y, z));} + + /// Push the camera along the view direction + void Push(float delta) + { + if(delta > focus) + delta = focus; + + focus -= delta; + + Dolly(d*delta); + } + + void Pan(float theta_x, float theta_y, float theta_z) + { + //x rotation is around the up axis + quaternion qx; + qx.CreateRotation(theta_x, up[0], up[1], up[2]); + + //y rotation is around the side axis + vec3 side = up.cross(d); + quaternion qy; + qy.CreateRotation(theta_y, side[0], side[1], side[2]); + + //z rotation is around the direction vector + quaternion qz; + qz.CreateRotation(theta_z, d[0], d[1], d[2]); + + //combine the rotations in x, y, z order + quaternion final = qz*qy*qx; + + //get the rotation matrix + matrix_sq rot_matrix = final.toMatrix3(); + + //apply the rotation + d = rot_matrix*d; + up = rot_matrix*up; + + //stabalize the camera + stabalize(); + + } + void Pan(float theta_x){Pan(theta_x, 0, 0);} + void Tilt(float theta_y){Pan(0, theta_y, 0);} + void Twist(float theta_z){Pan(0, 0, theta_z);} + + void Zoom(float delta) + { + fov -= delta; + if(fov < 0.5) + fov = 0.5; + if(fov > 180) + fov = 180; + } + + void OrbitFocus(float theta_x, float theta_y) + { + //find the focal point + vec3 focal_point = p + focus*d; + + //center the coordinate system on the focal point + vec3 centered = p - (focal_point - vec3(0, 0, 0)); + + //create the x rotation (around the up vector) + quaternion qx; + qx.CreateRotation(theta_x, up[0], up[1], up[2]); + centered = vec3(0, 0, 0) + qx.toMatrix3()*(centered - vec3(0, 0, 0)); + + //get a side vector for theta_y rotation + vec3 side = up.cross((vec3(0, 0, 0) - centered).norm()); + + quaternion qy; + qy.CreateRotation(theta_y, side[0], side[1], side[2]); + centered = vec3(0, 0, 0) + qy.toMatrix3()*(centered - vec3(0, 0, 0)); + + //perform the rotation on the centered camera position + //centered = final.toMatrix()*centered; + + //re-position the camera + p = centered + (focal_point - vec3(0, 0, 0)); + + //make sure we are looking at the focal point + LookAt(focal_point); + + //stabalize the camera + stabalize(); + + } + + void Slide(float u, float v) + { + vec3 V = up.norm(); + vec3 U = up.cross(d).norm(); + + p = p + (V * v) + (U * u); + } + + //accessor methods + vec3 getPosition(){return p;} + vec3 getUp(){return up;} + vec3 getDirection(){return d;} + vec3 getLookAt(){return p + focus*d;} + float getFOV(){return fov;} + + //output the camera settings + void print(std::ostream& output) + { + output<<"Position: "<(0, 0, 0); + d = vec3(0, 0, 1); + up = vec3(0, 1, 0); + focus = 1; + fov = 60; + + } + + /// Outputs the camera information as a string + std::string str(){ + std::stringstream ss; + ss<"<<(p + d * focus).str(); + return ss.str(); + } +}; + +} + + + +#endif diff --git a/tira/visualization/colormap.h b/tira/visualization/colormap.h new file mode 100644 index 0000000..15cc151 --- /dev/null +++ b/tira/visualization/colormap.h @@ -0,0 +1,414 @@ +#ifndef STIM_COLORMAP_H +#define STIM_COLORMAP_H + +#include +#include +#include + +#ifdef _WIN32 + #include +#endif + +#ifdef __CUDACC__ +#include "cublas_v2.h" +#include +#endif + +//saving an image to a file uses the CImg library + //this currently throws a lot of "unreachable" warnings (as of GCC 4.8.2, nvcc 6.5.12) +#include + + +#define BREWER_CTRL_PTS 11 + +static float BREWERCP[BREWER_CTRL_PTS*4] = {0.192157f, 0.211765f, 0.584314f, 1.0f, + 0.270588f, 0.458824f, 0.705882f, 1.0f, + 0.454902f, 0.678431f, 0.819608f, 1.0f, + 0.670588f, 0.85098f, 0.913725f, 1.0f, + 0.878431f, 0.952941f, 0.972549f, 1.0f, + 1.0f, 1.0f, 0.74902f, 1.0f, + 0.996078f, 0.878431f, 0.564706f, 1.0f, + 0.992157f, 0.682353f, 0.380392f, 1.0f, + 0.956863f, 0.427451f, 0.262745f, 1.0f, + 0.843137f, 0.188235f, 0.152941f, 1.0f, + 0.647059f, 0.0f, 0.14902f, 1.0f}; + +//static float BREWERCP[BREWER_CTRL_PTS * 4] = { 0.192157f, 0.584314f, 0.211765f, 1.0f, +// 0.270588f, 0.705882f, 0.458824f, 1.0f, +// 0.454902f, 0.819608f, 0.678431f, 1.0f, +// 0.670588f, 0.913725f, 0.85098f, 1.0f, +// 0.878431f, 0.972549f, 0.952941f, 1.0f, +// 1.0f, 0.74902f, 1.0f, 1.0f, +// 0.996078f, 0.878431f, 0.564706f, 1.0f, +// 0.992157f, 0.682353f, 0.380392f, 1.0f, +// 0.956863f, 0.427451f, 0.262745f, 1.0f, +// 0.843137f, 0.188235f, 0.152941f, 1.0f, +// 0.647059f, 0.0f, 0.14902f, 1.0f }; + +//static float BREWERCP[BREWER_CTRL_PTS * 4] = { 0.0f, 0.407843f, 0.215686f, 1.0f, +// 0.101960f, 0.596078f, 0.313725f, 1.0f, +// 0.4f, 0.741176f, 0.388235f, 1.0f, +// 0.650980f, 0.850980f, 0.415686f, 1.0f, +// 0.850980f, 0.937254f, 0.545098f, 1.0f, +// 1.0f, 1.0f, 0.749019f, 1.0f, +// 0.996078f, 0.878431f, 0.545098f, 1.0f, +// 0.992156f, 0.682352f, 0.380392f, 1.0f, +// 0.956862f, 0.427450f, 0.262745f, 1.0f, +// 0.843137f, 0.188235f, 0.152941f, 1.0f, +// 0.647058f, 0.0f, 0.149019f, 1.0f }; + + +#ifdef __CUDACC__ +texture cudaTexBrewer; +static cudaArray* gpuBrewer; +#endif + +namespace stim{ + +enum colormapType {cmBrewer, cmGrayscale, cmRainbow}; + +static void buffer2image(unsigned char* buffer, std::string filename, size_t width, size_t height) +{ + /*unsigned char* non_interleaved = (unsigned char*)malloc(x_size * y_size * 3); + unsigned int S = x_size * y_size; + + for(unsigned int i = 0; i < S; i++){ + non_interleaved[i + 0 * S] = buffer[i * 3 + 0]; + non_interleaved[i + 1 * S] = buffer[i * 3 + 1]; + non_interleaved[i + 2 * S] = buffer[i * 3 + 2]; + }*/ + + //create an image object + //cimg_library::CImg image(non_interleaved, x_size, y_size, 1, 3); + //image.save(filename.c_str()); + image I; + I.set_interleaved_rgb(buffer, width, height); + I.save(filename); +} + +#ifdef __CUDACC__ +static void initBrewer() +{ + //initialize the Brewer colormap + + //allocate CPU space + float4 cpuColorMap[BREWER_CTRL_PTS]; + + //define control rtsPoints + cpuColorMap[0] = make_float4(0.192157f, 0.211765f, 0.584314f, 1.0f); + cpuColorMap[1] = make_float4(0.270588f, 0.458824f, 0.705882f, 1.0f); + cpuColorMap[2] = make_float4(0.454902f, 0.678431f, 0.819608f, 1.0f); + cpuColorMap[3] = make_float4(0.670588f, 0.85098f, 0.913725f, 1.0f); + cpuColorMap[4] = make_float4(0.878431f, 0.952941f, 0.972549f, 1.0f); + cpuColorMap[5] = make_float4(1.0f, 1.0f, 0.74902f, 1.0f); + cpuColorMap[6] = make_float4(0.996078f, 0.878431f, 0.564706f, 1.0f); + cpuColorMap[7] = make_float4(0.992157f, 0.682353f, 0.380392f, 1.0f); + cpuColorMap[8] = make_float4(0.956863f, 0.427451f, 0.262745f, 1.0f); + cpuColorMap[9] = make_float4(0.843137f, 0.188235f, 0.152941f, 1.0f); + cpuColorMap[10] = make_float4(0.647059f, 0.0f, 0.14902f, 1.0f); + + + int width = BREWER_CTRL_PTS; + int height = 0; + + + // allocate array and copy colormap data + cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc(32, 32, 32, 32, cudaChannelFormatKindFloat); + + HANDLE_ERROR(cudaMallocArray(&gpuBrewer, &channelDesc, width, height)); + + HANDLE_ERROR(cudaMemcpyToArray(gpuBrewer, 0, 0, cpuColorMap, sizeof(float4)*width, cudaMemcpyHostToDevice)); + + // set texture parameters + cudaTexBrewer.addressMode[0] = cudaAddressModeClamp; + //texBrewer.addressMode[1] = cudaAddressModeClamp; + cudaTexBrewer.filterMode = cudaFilterModeLinear; + cudaTexBrewer.normalized = true; // access with normalized texture coordinates + + // Bind the array to the texture + HANDLE_ERROR(cudaBindTextureToArray( cudaTexBrewer, gpuBrewer, channelDesc)); + +} + +static void destroyBrewer() +{ + HANDLE_ERROR(cudaFreeArray(gpuBrewer)); +} + +template +__global__ static void applyBrewer(T* gpuSource, unsigned char* gpuDest, unsigned int N, T minVal = 0, T maxVal = 1) +{ + + int i = blockIdx.y * gridDim.x * blockDim.x + blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + //compute the normalized value on [minVal maxVal] + float a = (gpuSource[i] - minVal) / (maxVal - minVal); + + //compensate for the additional space at the edges + a *= (T)(BREWER_CTRL_PTS - 1)/(T)(BREWER_CTRL_PTS); + + //lookup the color + float shift = (T)1/(2*BREWER_CTRL_PTS); + float4 color = tex1D(cudaTexBrewer, a+shift); + //float4 color = tex1D(cudaTexBrewer, a); + + gpuDest[i * 3 + 0] = 255 * color.x; + gpuDest[i * 3 + 1] = 255 * color.y; + gpuDest[i * 3 + 2] = 255 * color.z; +} + +template +__global__ static void applyGrayscale(T* gpuSource, unsigned char* gpuDest, unsigned int N, T minVal = 0, T maxVal = 1) +{ + int i = blockIdx.y * gridDim.x * blockDim.x + blockIdx.x * blockDim.x + threadIdx.x; + if(i >= N) return; + + //compute the normalized value on [minVal maxVal] + float a = (gpuSource[i] - minVal) / (maxVal - minVal); + + //threshold + if(a > 1) + a = 1; + if(a < 0) + a = 0; + + gpuDest[i * 3 + 0] = 255 * a; + gpuDest[i * 3 + 1] = 255 * a; + gpuDest[i * 3 + 2] = 255 * a; +} + +template +static void gpu2gpu(T* gpuSource, unsigned char* gpuDest, unsigned int nVals, T minVal = 0, T maxVal = 1, colormapType cm = cmGrayscale, int blockDim = 128) +{ + //This function converts a scalar field on the GPU to a color image on the GPU + int gridX = (nVals + blockDim - 1)/blockDim; + int gridY = 1; + if(gridX > 65535) + { + gridY = (gridX + 65535 - 1) / 65535; + gridX = 65535; + } + dim3 dimGrid(gridX, gridY); + if(cm == cmGrayscale) + applyGrayscale<<>>(gpuSource, gpuDest, nVals, minVal, maxVal); + else if(cm == cmBrewer) + { + initBrewer(); + applyBrewer<<>>(gpuSource, gpuDest, nVals, minVal, maxVal); + destroyBrewer(); + } + +} + +template +static void gpu2cpu(T* gpuSource, unsigned char* cpuDest, unsigned int nVals, T minVal, T maxVal, colormapType cm = cmGrayscale) +{ + //this function converts a scalar field on the GPU to a color image on the CPU + + //first create the color image on the GPU + + //allocate GPU memory for the color image + unsigned char* gpuDest; + HANDLE_ERROR(cudaMalloc( (void**)&gpuDest, sizeof(unsigned char) * nVals * 3 )); + + //create the image on the gpu + gpu2gpu(gpuSource, gpuDest, nVals, minVal, maxVal, cm); + + //copy the image from the GPU to the CPU + HANDLE_ERROR(cudaMemcpy(cpuDest, gpuDest, sizeof(unsigned char) * nVals * 3, cudaMemcpyDeviceToHost)); + + HANDLE_ERROR(cudaFree( gpuDest )); + +} + +template +static void gpu2image(T* gpuSource, std::string fileDest, unsigned int x_size, unsigned int y_size, T valMin, T valMax, colormapType cm = cmGrayscale) +{ + //allocate a color buffer + unsigned char* cpuBuffer = NULL; + cpuBuffer = (unsigned char*) malloc(sizeof(unsigned char) * 3 * x_size * y_size); + + //do the mapping + gpu2cpu(gpuSource, cpuBuffer, x_size * y_size, valMin, valMax, cm); + + //copy the buffer to an image + buffer2image(cpuBuffer, fileDest, x_size, y_size); + + free(cpuBuffer); +} + +/// save a GPU image to a file using automatic scaling +template +static void gpu2image(T* gpuSource, std::string fileDest, unsigned int x_size, unsigned int y_size, colormapType cm = cmGrayscale){ + size_t N = x_size * y_size; //calculate the total number of elements in the image + + cublasStatus_t stat; + cublasHandle_t handle; + + stat = cublasCreate(&handle); //create a cuBLAS handle + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS initialization failed\n"); + exit(1); + } + + int i_min, i_max; + stat = cublasIsamin(handle, (int)N, gpuSource, 1, &i_min); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate minimum r value.\n"); + exit(1); + } + stat = cublasIsamax(handle, (int)N, gpuSource, 1, &i_max); + if (stat != CUBLAS_STATUS_SUCCESS){ //test for failure + printf ("CUBLAS Error: failed to calculate maximum r value.\n"); + exit(1); + } + cublasDestroy(handle); + + i_min--; //cuBLAS uses 1-based indexing for Fortran compatibility + i_max--; + T v_min, v_max; //allocate space to store the minimum and maximum values + HANDLE_ERROR( cudaMemcpy(&v_min, gpuSource + i_min, sizeof(T), cudaMemcpyDeviceToHost) ); //copy the min and max values from the device to the CPU + HANDLE_ERROR( cudaMemcpy(&v_max, gpuSource + i_max, sizeof(T), cudaMemcpyDeviceToHost) ); + + + + gpu2image(gpuSource, fileDest, x_size, y_size, min(v_min, v_max), max(v_min, v_max), cm); +} + +#endif + +template +static void cpuApplyBrewer(T* cpuSource, unsigned char* cpuDest, size_t N, T minVal = 0, T maxVal = 1) +{ + for(size_t i=0; i 1) a = 1; + + float c = a * (float)(BREWER_CTRL_PTS-1); + int ptLow = (int)c; + float m = c - (float)ptLow; + //std::cout< +static void cpu2cpu(T* cpuSource, unsigned char* cpuDest, size_t nVals, T valMin, T valMax, colormapType cm = cmGrayscale) +{ + + if(cm == cmBrewer) + cpuApplyBrewer(cpuSource, cpuDest, nVals, valMin, valMax); + else if(cm == cmGrayscale) + { + int i; + float a; + float range = valMax - valMin; + + for(i = 0; i 1) a = 1; + + cpuDest[i * 3 + 0] = (unsigned char)(255 * a); + cpuDest[i * 3 + 1] = (unsigned char)(255 * a); + cpuDest[i * 3 + 2] = (unsigned char)(255 * a); + } + } +} + +template +static void cpu2cpu(T* cpuSource, unsigned char* cpuDest, unsigned long long nVals, colormapType cm = cmGrayscale) +{ + //computes the max and min range automatically + + //find the largest magnitude value + T maxVal = cpuSource[0]; + T minVal = cpuSource[0]; + for(int i=1; i maxVal) + maxVal = cpuSource[i]; + if(cpuSource[i] < minVal) + minVal = cpuSource[i]; + } + + cpu2cpu(cpuSource, cpuDest, nVals, minVal, maxVal, cm); + +} + + + +template +static void cpu2image(T* cpuSource, std::string fileDest, size_t x_size, size_t y_size, T valMin, T valMax, colormapType cm = cmGrayscale) +{ + //allocate a color buffer + unsigned char* cpuBuffer = (unsigned char*) malloc(sizeof(unsigned char) * 3 * x_size * y_size); + + //do the mapping + cpu2cpu(cpuSource, cpuBuffer, x_size * y_size, valMin, valMax, cm); + + //copy the buffer to an image + buffer2image(cpuBuffer, fileDest, x_size, y_size); + + free(cpuBuffer); + +} + +template +static void cpu2image(T* cpuSource, std::string fileDest, size_t x_size, size_t y_size, colormapType cm = cmGrayscale) +{ + //allocate a color buffer + unsigned char* cpuBuffer = (unsigned char*) malloc(sizeof(unsigned char) * 3 * x_size * y_size); + + //do the mapping + cpu2cpu(cpuSource, cpuBuffer, x_size * y_size, cm); + + //copy the buffer to an image + buffer2image(cpuBuffer, fileDest, x_size, y_size); + + free(cpuBuffer); + +} + +} //end namespace colormap and rts + +#endif + diff --git a/tira/visualization/cylinder.h b/tira/visualization/cylinder.h new file mode 100644 index 0000000..f70c679 --- /dev/null +++ b/tira/visualization/cylinder.h @@ -0,0 +1,731 @@ +#ifndef STIM_CYLINDER_H +#define STIM_CYLINDER_H +#include +#include +#include +#include + + +namespace stim +{ +template +class cylinder : public centerline { +protected: + + using stim::centerline::d; + + std::vector< stim::vec3 > U; //stores the array of U vectors defining the Frenet frame + std::vector< T > R; //stores a list of magnitudes for each point in the centerline (assuming mags[0] is the radius) + + using stim::centerline::findIdx; + + void init() { + U.resize(size()); //allocate space for the frenet frame vectors +// if (R.size() != 0) + R.resize(size()); + + stim::circle c; //create a circle + stim::vec3 d0, d1; + for (size_t i = 0; i < size() - 1; i++) { //for each line segment in the centerline + c.rotate(d(i)); //rotate the circle to match that normal + U[i] = c.U; //save the U vector from the circle + } + U[size() - 1] = c.U; //for the last point, duplicate the final frenet frame vector + } + + //calculates the U values for each point to initialize the frenet frame + // this function assumes that the centerline has already been set + +public: + + using stim::centerline::size; + using stim::centerline::at; + using stim::centerline::L; + using stim::centerline::length; + + cylinder() : centerline(){} + + cylinder(centerlinec) : centerline(c) { + init(); + } + + cylinder(std::vector > p, std::vector s) + : centerline(p) + { + R = s; + init(); + } + + cylinder(stim::centerline p, std::vector s) + { + //was d = s; + p = s; + init(); + } + + //cylinder(centerlinec, T r) : centerline(c) { + // init(); + // //add_mag(r); + //} + + //initialize a cylinder with a list of points and magnitude values + //cylinder(centerlinec, std::vector r) : centerline(c){ + // init(); + // //add_mag(r); + //} + + //copy the original radius + void copy_r(std::vector radius) { + for (unsigned i = 0; i < radius.size(); i++) + R[i] = radius[i]; + } + + ///Returns magnitude i at the given length into the fiber (based on the pvalue). + ///Interpolates the position along the line. + ///@param l: the location of the in the cylinder. + ///@param idx: integer location of the point closest to l but prior to it. + T r(T l, int idx) { + T a = (l - L[idx]) / (L[idx + 1] - L[idx]); + T v2 = (R[idx] + (R[idx + 1] - R[idx])*a); + + return v2; + } + + ///Returns the ith magnitude at the given p-value (p value ranges from 0 to 1). + ///interpolates the radius along the line. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + T rl(T pvalue) { + if (pvalue <= 0.0) return R[0]; + if (pvalue >= 1.0) return R[size() - 1]; + + T l = pvalue*L[L.size() - 1]; + int idx = findIdx(l); + return r(l, idx); + } + + /// Returns the magnitude at the given index + /// @param i is the index of the desired point + /// @param r is the index of the magnitude value + T r(unsigned i) { + return R[i]; + } + + + ///adds a magnitude to each point in the cylinder + /*void add_mag(V val = 0) { + if (M.size() == 0) M.resize(size()); //if the magnitude vector isn't initialized, resize it to match the centerline + for (size_t i = 0; i < size(); i++) //for each point + R[i].push_back(val); //add this value to the magnitude vector at each point + }*/ + + //adds a magnitude based on a list of magnitudes for each point + /*void add_mag(std::vector val) { + if (M.size() == 0) M.resize(size()); //if the magnitude vector isn't initialized, resize it to match the centerline + for (size_t i = 0; i < size(); i++) //for each point + R[i].push_back(val[i]); //add this value to the magnitude vector at each point + }*/ + + //sets the value of magnitude m at point i + void set_r(size_t i, T r) { + R[i] = r; + } + + /*size_t nmags() { + if (M.size() == 0) return 0; + else return R[0].size(); + }*/ + + ///Returns a circle representing the cylinder cross section at point i + stim::circle circ(size_t i) { + return stim::circle(at(i), R[i], d(i), U[i]); + } + + ///Returns an OBJ object representing the cylinder with a radial tesselation value of N using magnitude m + stim::obj OBJ(size_t N) { + stim::obj out; //create an OBJ object + stim::circle c0, c1; + std::vector< stim::vec3 > p0, p1; //allocate space for the point sets representing the circles bounding each cylinder segment + T u0, u1, v0, v1; //allocate variables to store running texture coordinates + for (size_t i = 1; i < size(); i++) { //for each line segment in the cylinder + c0 = circ(i - 1); //get the two circles bounding the line segment + c1 = circ(i); + + p0 = c0.points(N); //get t points for each of the end caps + p1 = c1.points(N); + + u0 = L[i - 1] / length(); //calculate the texture coordinate (u, v) where u runs along the cylinder length + u1 = L[i] / length(); + + for (size_t n = 1; n < N; n++) { //for each point in the circle + v0 = (double)(n-1) / (double)(N - 1); //v texture coordinate runs around the cylinder + v1 = (double)(n) / (double)(N - 1); + out.Begin(OBJ_FACE); //start a face (quad) + out.TexCoord(u0, v0); + out.Vertex(p0[n - 1][0], p0[n - 1][1], p0[n - 1][2]); //output the points composing a strip of quads wrapping the cylinder segment + out.TexCoord(u1, v0); + out.Vertex(p1[n - 1][0], p1[n - 1][1], p1[n - 1][2]); + + out.TexCoord(u0, v1); + out.Vertex(p1[n][0], p1[n][1], p1[n][2]); + out.TexCoord(u1, v1); + out.Vertex(p0[n][0], p0[n][1], p0[n][2]); + out.End(); + } + v0 = (double)(N - 2) / (double)(N - 1); //v texture coordinate runs around the cylinder + v1 = 1.0; + out.Begin(OBJ_FACE); + out.TexCoord(u0, v0); + out.Vertex(p0[N - 1][0], p0[N - 1][1], p0[N - 1][2]); //output the points composing a strip of quads wrapping the cylinder segment + out.TexCoord(u1, v0); + out.Vertex(p1[N - 1][0], p1[N - 1][1], p1[N - 1][2]); + + out.TexCoord(u0, v1); + out.Vertex(p1[0][0], p1[0][1], p1[0][2]); + out.TexCoord(u1, v1); + out.Vertex(p0[0][0], p0[0][1], p0[0][2]); + out.End(); + } + return out; + } + + std::string str() { + std::stringstream ss; + size_t N = std::vector< stim::vec3 >::size(); + ss << "---------[" << N << "]---------" << std::endl; + for (size_t i = 0; i < N; i++) + ss << std::vector< stim::vec3 >::at(i) << " r = " << R[i] << " u = " << U[i] << std::endl; + ss << "--------------------" << std::endl; + + return ss.str(); + } + + /// Integrates a magnitude value along the cylinder. + /// @param m is the magnitude value to be integrated (this is usually the radius) + T integrate() { + T sum = 0; //initialize the integral to zero + if (L.size() == 1) + return sum; + else { + T m0, m1; //allocate space for both magnitudes in a single segment + m0 = R[0]; //initialize the first point and magnitude to the first point in the cylinder + T len = L[1]; //allocate space for the segment length + + + for (unsigned p = 1; p < size(); p++) { //for every consecutive point in the cylinder + m1 = R[p]; + if (p > 1) len = (L[p] - L[p - 1]); //calculate the segment length using the L array + sum += (m0 + m1) / (T)2.0 * len; //add the average magnitude, weighted by the segment length + m0 = m1; //move to the next segment by shifting points + } + return sum; //return the integral + } + } + + /// Resamples the cylinder to provide a maximum distance of "spacing" between centerline points. All current + /// centerline points are guaranteed to exist in the new cylinder + /// @param spacing is the maximum spacing allowed between sample points + cylinder resample(T spacing) { + cylinder c = stim::centerline::resample(spacing); //resample the centerline and use it to create a new cylinder + + //size_t nm = nmags(); //get the number of magnitude values in the current cylinder + //if (nm > 0) { //if there are magnitude values + // std::vector magvec(nm, 0); //create a magnitude vector for a single point + // c.M.resize(c.size(), magvec); //allocate space for a magnitude vector at each point of the new cylinder + //} + + T l, t; + for (size_t i = 0; i < c.size(); i++) { //for each point in the new cylinder + l = c.L[i]; //get the length along the new cylinder + t = l / length(); //calculate the parameter value along the new cylinder + //for (size_t mag = 0; mag < nm; mag++) { //for each magnitude value + c.R[i] = r(t); //retrieve the interpolated magnitude from the current cylinder and store it in the new one + //} + } + return c; + } + + std::vector< stim::cylinder > split(unsigned int idx) { + + unsigned N = size(); + std::vector< stim::centerline > LL; + LL.resize(2); + LL = (*this).centerline::split(idx); + std::vector< stim::cylinder > C(LL.size()); + unsigned i = 0; + C[0] = LL[0]; + //C[0].R.resize(idx); + for (; i < idx + 1; i++) { + //for(unsigned d = 0; d < 3; d++) + //C[0][i][d] = LL[0].c[i][d]; + C[0].R[i] = R[i]; + //C[0].R[i].resize(1); + } + if (C.size() == 2) { + C[1] = LL[1]; + i--; + //C[1].M.resize(N - idx); + for (; i < N; i++) { + //for(unsigned d = 0; d < 3; d++) + //C[1][i - idx][d] = LL[1].c[i - idx][d]; + C[1].R[i - idx] = R[i]; + //C[1].M[i - idx].resize(1); + } + } + + return C; + } + + + /* + ///inits the cylinder from a list of points (std::vector of stim::vec3 --inP) and magnitudes (inM) + void + init(centerline inP, std::vector< std::vector > inM) { + M = inM; //the magnitude vector can be copied directly + (*this) = inP; //the centerline can be copied to this class directly + stim::vec3 v1; + stim::vec3 v2; + e.resize(inP.size()); + + norms.resize(inP.size()); + Us.resize(inP.size()); + + if(inP.size() < 2) + return; + + //calculate each L. + L.resize(inP.size()); //the number of precomputed lengths will equal the number of points + T temp = (T)0; //length up to that point + L[0] = temp; + for(size_t i = 1; i < L.size(); i++) + { + temp += (inP[i-1] - inP[i]).len(); + L[i] = temp; + } + + stim::vec3 dr = (inP[1] - inP[0]).norm(); + s = stim::circle(inP[0], inR[0][0], dr, stim::vec3(1,0,0)); + norms[0] = s.N; + e[0] = s; + Us[0] = e[0].U; + for(size_t i = 1; i < inP.size()-1; i++) + { + s.center(inP[i]); + v1 = (inP[i] - inP[i-1]).norm(); + v2 = (inP[i+1] - inP[i]).norm(); + dr = (v1+v2).norm(); + s.normal(dr); + + norms[i] = s.N; + + s.scale(inR[i][0]/inR[i-1][0]); + e[i] = s; + Us[i] = e[i].U; + } + + int j = inP.size()-1; + s.center(inP[j]); + dr = (inP[j] - inP[j-1]).norm(); + s.normal(dr); + + norms[j] = s.N; + + s.scale(inR[j][0]/inR[j-1][0]); + e[j] = s; + Us[j] = e[j].U; + } + + ///returns the direction vector at point idx. + stim::vec3 + d(int idx) + { + if(idx == 0) + { + stim::vec3 temp( + c[idx+1][0]-c[idx][0], + c[idx+1][1]-c[idx][1], + c[idx+1][2]-c[idx][2] + ); +// return (e[idx+1].P - e[idx].P).norm(); + return (temp.norm()); + } + else if(idx == N-1) + { + stim::vec3 temp( + c[idx][0]-c[idx+1][0], + c[idx][1]-c[idx+1][1], + c[idx][2]-c[idx+1][2] + ); + // return (e[idx].P - e[idx-1].P).norm(); + return (temp.norm()); + } + else + { +// return (e[idx+1].P - e[idx].P).norm(); +// stim::vec3 v1 = (e[idx].P-e[idx-1].P).norm(); + stim::vec3 v1( + c[idx][0]-c[idx-1][0], + c[idx][1]-c[idx-1][1], + c[idx][2]-c[idx-1][2] + ); + +// stim::vec3 v2 = (e[idx+1].P-e[idx].P).norm(); + stim::vec3 v2( + c[idx+1][0]-c[idx][0], + c[idx+1][1]-c[idx][1], + c[idx+1][2]-c[idx][2] + ); + + return (v1.norm()+v2.norm()).norm(); + } + // return e[idx].N; + + } + + stim::vec3 + d(T l, int idx) + { + if(idx == 0 || idx == N-1) + { + return norms[idx]; +// return e[idx].N; + } + else + { + + T rat = (l-L[idx])/(L[idx+1]-L[idx]); + return( norms[idx] + (norms[idx+1] - norms[idx])*rat); +// return( e[idx].N + (e[idx+1].N - e[idx].N)*rat); + } + } + + + ///finds the index of the point closest to the length l on the lower bound. + ///binary search. + int + findIdx(T l) + { + unsigned int i = L.size()/2; + unsigned int max = L.size()-1; + unsigned int min = 0; + while(i > 0 && i < L.size()-1) + { +// std::cerr << "Trying " << i << std::endl; +// std::cerr << "l is " << l << ", L[" << i << "]" << L[i] << std::endl; + if(l < L[i]) + { + max = i; + i = min+(max-min)/2; + } + else if(L[i] <= l && L[i+1] >= l) + { + break; + } + else + { + min = i; + i = min+(max-min)/2; + } + } + return i; + } + + public: + ///default constructor + cylinder() + // : centerline() + { + + } + + ///constructor to create a cylinder from a set of points, radii, and the number of sides for the cylinder. + ///@param inP: Vector of stim vec3 composing the points of the centerline. + ///@param inM: Vector of stim vecs composing the radii of the centerline. + cylinder(std::vector > inP, std::vector > inM) + : centerline(inP) + { + init(inP, inM); + } + + ///constructor to create a cylinder from a set of points, radii, and the number of sides for the cylinder. + ///@param inP: Vector of stim vec3 composing the points of the centerline. + ///@param inM: Vector of stim vecs composing the radii of the centerline. + cylinder(std::vector > inP, std::vector< T > inM) + : centerline(inP) + { + std::vector > temp; + stim::vec zero(0.0,0.0); + temp.resize(inM.size(), zero); + for(int i = 0; i < inM.size(); i++) + temp[i][0] = inR[i]; + init(inP, temp); + } + + + ///Constructor defines a cylinder with centerline inP and magnitudes of zero + ///@param inP: Vector of stim vec3 composing the points of the centerline + cylinder(std::vector< stim::vec3 > inP) + : centerline(inP) + { + std::vector< stim::vec > inM; //create an array of arbitrary magnitudes + + stim::vec zero; + zero.push_back(0); + + inM.resize(inP.size(), zero); //initialize the magnitude values to zero + init(inP, inM); + } + + //assignment operator creates a cylinder from a centerline (default radius is zero) + cylinder& operator=(stim::centerline c) { + init(c); + } + + ///Returns the number of points on the cylinder centerline + + unsigned int size(){ + return N; + } + + + ///Returns a position vector at the given p-value (p value ranges from 0 to 1). + ///interpolates the position along the line. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + stim::vec3 + p(T pvalue) + { + if(pvalue < 0.0 || pvalue > 1.0) + { + return stim::vec3(-1,-1,-1); + } + T l = pvalue*L[L.size()-1]; + int idx = findIdx(l); + return (p(l,idx)); + } + + ///Returns a position vector at the given length into the fiber (based on the pvalue). + ///Interpolates the radius along the line. + ///@param l: the location of the in the cylinder. + ///@param idx: integer location of the point closest to l but prior to it. + stim::vec3 + p(T l, int idx) + { + T rat = (l-L[idx])/(L[idx+1]-L[idx]); + stim::vec3 v1( + c[idx][0], + c[idx][1], + c[idx][2] + ); + + stim::vec3 v2( + c[idx+1][0], + c[idx+1][1], + c[idx+1][2] + ); +// return( e[idx].P + (e[idx+1].P-e[idx].P)*rat); + return( v1 + (v2-v1)*rat); +// return( +// return (pos[idx] + (pos[idx+1]-pos[idx])*((l-L[idx])/(L[idx+1]- L[idx]))); + } + + ///Returns a radius at the given p-value (p value ranges from 0 to 1). + ///interpolates the radius along the line. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + T + r(T pvalue) + { + if(pvalue < 0.0 || pvalue > 1.0){ + std::cerr<<"Error, value "<= 10e-6) + { + std::cout << "-------------------------" << std::endl; + std::cout << e[idx].str() << std::endl << std::endl; + std::cout << Us[idx].str() << std::endl; + std::cout << (float)v1 - (float) v2 << std::endl; + std::cout << "failed" << std::endl; + } +// std::cout << e[idx].U.len() << " " << mags[idx][0] << std::endl; +// std::cout << v2 << std::endl; + return(v2); +// return (mags[idx][0] + (mags[idx+1][0]-mags[idx][0])*rat); + // ( + } + + /// Returns the magnitude at the given index + /// @param i is the index of the desired point + /// @param m is the index of the magnitude value + T ri(unsigned i, unsigned m = 0){ + return mags[i][m]; + } + + /// Adds a new magnitude value to all points + /// @param m is the starting value for the new magnitude + void add_mag(T m = 0){ + for(unsigned int p = 0; p < N; p++) + mags[p].push_back(m); + } + + /// Sets a magnitude value + /// @param val is the new value for the magnitude + /// @param p is the point index for the magnitude to be set + /// @param m is the index for the magnitude + void set_mag(T val, unsigned p, unsigned m = 0){ + mags[p][m] = val; + } + + /// Returns the number of magnitude values at each point + unsigned nmags(){ + return mags[0].size(); + } + + ///returns the position of the point with a given pvalue and theta on the surface + ///in x, y, z coordinates. Theta is in degrees from 0 to 360. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + ///@param theta: the angle to the point of a circle. + stim::vec3 + surf(T pvalue, T theta) + { + if(pvalue < 0.0 || pvalue > 1.0) + { + return stim::vec3(-1,-1,-1); + } else { + T l = pvalue*L[L.size()-1]; + int idx = findIdx(l); + stim::vec3 ps = p(l, idx); + T m = r(l, idx); + s = e[idx]; + s.center(ps); + s.normal(d(l, idx)); + s.scale(m/e[idx].U.len()); + return(s.p(theta)); + } + } + + ///returns a vector of points necessary to create a circle at every position in the fiber. + ///@param sides: the number of sides of each circle. + std::vector > > + getPoints(int sides) + { + std::vector > > points; + points.resize(N); + for(int i = 0; i < N; i++) + { + points[i] = e[i].getPoints(sides); + } + return points; + } + + ///returns the total length of the line at index j. + T + getl(int j) + { + return (L[j]); + } + /// Allows a point on the centerline to be accessed using bracket notation + + vec3 operator[](unsigned int i){ + return e[i].P; + } + + /// Returns the total length of the cylinder centerline + T length(){ + return L.back(); + } + + /// Integrates a magnitude value along the cylinder. + /// @param m is the magnitude value to be integrated (this is usually the radius) + T integrate(unsigned m = 0){ + + T M = 0; //initialize the integral to zero + T m0, m1; //allocate space for both magnitudes in a single segment + + //vec3 p0, p1; //allocate space for both points in a single segment + + m0 = mags[0][m]; //initialize the first point and magnitude to the first point in the cylinder + //p0 = pos[0]; + + T len = L[0]; //allocate space for the segment length + + //for every consecutive point in the cylinder + for(unsigned p = 1; p < N; p++){ + + //p1 = pos[p]; //get the position and magnitude for the next point + m1 = mags[p][m]; + + if(p > 1) len = (L[p-1] - L[p-2]); //calculate the segment length using the L array + + //add the average magnitude, weighted by the segment length + M += (m0 + m1)/(T)2.0 * len; + + m0 = m1; //move to the next segment by shifting points + } + return M; //return the integral + } + + /// Averages a magnitude value across the cylinder + /// @param m is the magnitude value to be averaged (this is usually the radius) + T average(unsigned m = 0){ + + //return the average magnitude + return integrate(m) / L.back(); + } + + /// Resamples the cylinder to provide a maximum distance of "spacing" between centerline points. All current + /// centerline points are guaranteed to exist in the new cylinder + /// @param spacing is the maximum spacing allowed between sample points + cylinder resample(T spacing){ + + std::vector< vec3 > result; + + vec3 p0 = e[0].P; //initialize p0 to the first point on the centerline + vec3 p1; + unsigned N = size(); //number of points in the current centerline + + //for each line segment on the centerline + for(unsigned int i = 1; i < N; i++){ + p1 = e[i].P; //get the second point in the line segment + + vec3 v = p1 - p0; //calculate the vector between these two points + T d = v.len(); //calculate the distance between these two points (length of the line segment) + + size_t nsteps = (size_t)std::ceil(d / spacing); //calculate the number of steps to take along the segment to meet the spacing criteria + T stepsize = (T)1.0 / nsteps; //calculate the parametric step size between new centerline points + + //for each step along the line segment + for(unsigned s = 0; s < nsteps; s++){ + T alpha = stepsize * s; //calculate the fraction of the distance along the line segment covered + result.push_back(p0 + alpha * v); //push the point at alpha position along the line segment + } + + p0 = p1; //shift the points to move to the next line segment + } + + result.push_back(e[size() - 1].P); //push the last point in the centerline + + return cylinder(result); + + }*/ + + +}; + +} +#endif diff --git a/tira/visualization/cylinder_dep.h b/tira/visualization/cylinder_dep.h new file mode 100644 index 0000000..26d27bd --- /dev/null +++ b/tira/visualization/cylinder_dep.h @@ -0,0 +1,498 @@ +#ifndef STIM_CYLINDER_H +#define STIM_CYLINDER_H +#include +#include +#include + + +namespace stim +{ +template +class cylinder + : public centerline +{ + private: + stim::circle s; //an arbitrary circle + std::vector > e; //an array of circles that store the centerline + + std::vector > norms; + std::vector > Us; + std::vector > mags; //stores a list of magnitudes for each point in the centerline (assuming mags[0] is the radius) + std::vector< T > L; //length of the cylinder at each position (pre-integration) + + + using stim::centerline::c; + using stim::centerline::N; + + ///default init + void + init() + { + + } + + ///inits the cylinder from a list of points (std::vector of stim::vec3 --inP) and radii (inM) + void + init(std::vector > inP, std::vector > inM) + { + mags = inM; + stim::vec3 v1; + stim::vec3 v2; + e.resize(inP.size()); + + norms.resize(inP.size()); + Us.resize(inP.size()); + + if(inP.size() < 2) + return; + + //calculate each L. + L.resize(inP.size()); //the number of precomputed lengths will equal the number of points + T temp = (T)0; //length up to that point + L[0] = temp; + for(size_t i = 1; i < L.size(); i++) + { + temp += (inP[i-1] - inP[i]).len(); + L[i] = temp; + } + + stim::vec3 dr = (inP[1] - inP[0]).norm(); + s = stim::circle(inP[0], inM[0][0], dr, stim::vec3(1,0,0)); + norms[0] = s.N; + e[0] = s; + Us[0] = e[0].U; + for(size_t i = 1; i < inP.size()-1; i++) + { + s.center(inP[i]); + v1 = (inP[i] - inP[i-1]).norm(); + v2 = (inP[i+1] - inP[i]).norm(); + dr = (v1+v2).norm(); + s.normal(dr); + + norms[i] = s.N; + + s.scale(inM[i][0]/inM[i-1][0]); + e[i] = s; + Us[i] = e[i].U; + } + + int j = inP.size()-1; + s.center(inP[j]); + dr = (inP[j] - inP[j-1]).norm(); + s.normal(dr); + + norms[j] = s.N; + + s.scale(inM[j][0]/inM[j-1][0]); + e[j] = s; + Us[j] = e[j].U; + } + + ///returns the direction vector at point idx. + stim::vec3 + d(int idx) + { + if(idx == 0) + { + stim::vec3 temp( + c[idx+1][0]-c[idx][0], + c[idx+1][1]-c[idx][1], + c[idx+1][2]-c[idx][2] + ); +// return (e[idx+1].P - e[idx].P).norm(); + return (temp.norm()); + } + else if(idx == N-1) + { + stim::vec3 temp( + c[idx][0]-c[idx+1][0], + c[idx][1]-c[idx+1][1], + c[idx][2]-c[idx+1][2] + ); + // return (e[idx].P - e[idx-1].P).norm(); + return (temp.norm()); + } + else + { +// return (e[idx+1].P - e[idx].P).norm(); +// stim::vec3 v1 = (e[idx].P-e[idx-1].P).norm(); + stim::vec3 v1( + c[idx][0]-c[idx-1][0], + c[idx][1]-c[idx-1][1], + c[idx][2]-c[idx-1][2] + ); + +// stim::vec3 v2 = (e[idx+1].P-e[idx].P).norm(); + stim::vec3 v2( + c[idx+1][0]-c[idx][0], + c[idx+1][1]-c[idx][1], + c[idx+1][2]-c[idx][2] + ); + + return (v1.norm()+v2.norm()).norm(); + } + // return e[idx].N; + + } + + stim::vec3 + d(T l, int idx) + { + if(idx == 0 || idx == N-1) + { + return norms[idx]; +// return e[idx].N; + } + else + { + + T rat = (l-L[idx])/(L[idx+1]-L[idx]); + return( norms[idx] + (norms[idx+1] - norms[idx])*rat); +// return( e[idx].N + (e[idx+1].N - e[idx].N)*rat); + } + } + + + ///finds the index of the point closest to the length l on the lower bound. + ///binary search. + int + findIdx(T l) + { + unsigned int i = L.size()/2; + unsigned int max = L.size()-1; + unsigned int min = 0; + while(i > 0 && i < L.size()-1) + { +// std::cerr << "Trying " << i << std::endl; +// std::cerr << "l is " << l << ", L[" << i << "]" << L[i] << std::endl; + if(l < L[i]) + { + max = i; + i = min+(max-min)/2; + } + else if(L[i] <= l && L[i+1] >= l) + { + break; + } + else + { + min = i; + i = min+(max-min)/2; + } + } + return i; + } + + public: + ///default constructor + cylinder() + // : centerline() + { + + } + + ///constructor to create a cylinder from a set of points, radii, and the number of sides for the cylinder. + ///@param inP: Vector of stim vec3 composing the points of the centerline. + ///@param inM: Vector of stim vecs composing the radii of the centerline. + cylinder(std::vector > inP, std::vector > inM) + : centerline(inP) + { + init(inP, inM); + } + + ///constructor to create a cylinder from a set of points, radii, and the number of sides for the cylinder. + ///@param inP: Vector of stim vec3 composing the points of the centerline. + ///@param inM: Vector of stim vecs composing the radii of the centerline. + cylinder(std::vector > inP, std::vector< T > inM) + : centerline(inP) + { + std::vector > temp; + stim::vec zero(0.0,0.0); + temp.resize(inM.size(), zero); + for(int i = 0; i < inM.size(); i++) + temp[i][0] = inM[i]; + init(inP, temp); + } + + + ///Constructor defines a cylinder with centerline inP and magnitudes of zero + ///@param inP: Vector of stim vec3 composing the points of the centerline + cylinder(std::vector< stim::vec3 > inP) + : centerline(inP) + { + std::vector< stim::vec > inM; //create an array of arbitrary magnitudes + + stim::vec zero; + zero.push_back(0); + + inM.resize(inP.size(), zero); //initialize the magnitude values to zero + init(inP, inM); + } + + ///Returns the number of points on the cylinder centerline + + unsigned int size(){ + return N; + } + + + ///Returns a position vector at the given p-value (p value ranges from 0 to 1). + ///interpolates the position along the line. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + stim::vec3 + p(T pvalue) + { + if(pvalue < 0.0 || pvalue > 1.0) + { + return stim::vec3(-1,-1,-1); + } + T l = pvalue*L[L.size()-1]; + int idx = findIdx(l); + return (p(l,idx)); +/* T rat = (l-L[idx])/(L[idx+1]-L[idx]); + stim::vec3 v1( + c[idx][0], + c[idx][1], + c[idx][2] + ); + + stim::vec3 v2( + c[idx+1][0], + c[idx+1][1], + c[idx+1][2] + ); + +// return( e[idx].P + (e[idx+1].P-e[idx].P)*rat); + return( v1 + (v2 - v1)*rat); +*/ } + + ///Returns a position vector at the given length into the fiber (based on the pvalue). + ///Interpolates the radius along the line. + ///@param l: the location of the in the cylinder. + ///@param idx: integer location of the point closest to l but prior to it. + stim::vec3 + p(T l, int idx) + { + T rat = (l-L[idx])/(L[idx+1]-L[idx]); + stim::vec3 v1( + c[idx][0], + c[idx][1], + c[idx][2] + ); + + stim::vec3 v2( + c[idx+1][0], + c[idx+1][1], + c[idx+1][2] + ); +// return( e[idx].P + (e[idx+1].P-e[idx].P)*rat); + return( v1 + (v2-v1)*rat); +// return( +// return (pos[idx] + (pos[idx+1]-pos[idx])*((l-L[idx])/(L[idx+1]- L[idx]))); + } + + ///Returns a radius at the given p-value (p value ranges from 0 to 1). + ///interpolates the radius along the line. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + T + r(T pvalue) + { + if(pvalue < 0.0 || pvalue > 1.0){ + std::cerr<<"Error, value "<= 10e-6) + { + std::cout << "-------------------------" << std::endl; + std::cout << e[idx].str() << std::endl << std::endl; + std::cout << Us[idx].str() << std::endl; + std::cout << (float)v1 - (float) v2 << std::endl; + std::cout << "failed" << std::endl; + } +// std::cout << e[idx].U.len() << " " << mags[idx][0] << std::endl; +// std::cout << v2 << std::endl; + return(v2); +// return (mags[idx][0] + (mags[idx+1][0]-mags[idx][0])*rat); + // ( + } + + /// Returns the magnitude at the given index + /// @param i is the index of the desired point + /// @param m is the index of the magnitude value + T ri(unsigned i, unsigned m = 0){ + return mags[i][m]; + } + + /// Adds a new magnitude value to all points + /// @param m is the starting value for the new magnitude + void add_mag(T m = 0){ + for(unsigned int p = 0; p < N; p++) + mags[p].push_back(m); + } + + /// Sets a magnitude value + /// @param val is the new value for the magnitude + /// @param p is the point index for the magnitude to be set + /// @param m is the index for the magnitude + void set_mag(T val, unsigned p, unsigned m = 0){ + mags[p][m] = val; + } + + /// Returns the number of magnitude values at each point + unsigned nmags(){ + return mags[0].size(); + } + + ///returns the position of the point with a given pvalue and theta on the surface + ///in x, y, z coordinates. Theta is in degrees from 0 to 360. + ///@param pvalue: the location of the in the cylinder, from 0 (beginning to 1). + ///@param theta: the angle to the point of a circle. + stim::vec3 + surf(T pvalue, T theta) + { + if(pvalue < 0.0 || pvalue > 1.0) + { + return stim::vec3(-1,-1,-1); + } else { + T l = pvalue*L[L.size()-1]; + int idx = findIdx(l); + stim::vec3 ps = p(l, idx); + T m = r(l, idx); + s = e[idx]; + s.center(ps); + s.normal(d(l, idx)); + s.scale(m/e[idx].U.len()); + return(s.p(theta)); + } + } + + ///returns a vector of points necessary to create a circle at every position in the fiber. + ///@param sides: the number of sides of each circle. + std::vector > > + getPoints(int sides) + { + std::vector > > points; + points.resize(N); + for(int i = 0; i < N; i++) + { + points[i] = e[i].getPoints(sides); + } + return points; + } + + ///returns the total length of the line at index j. + T + getl(int j) + { + return (L[j]); + } + /// Allows a point on the centerline to be accessed using bracket notation + + vec3 operator[](unsigned int i){ + return e[i].P; + } + + /// Returns the total length of the cylinder centerline + T length(){ + return L.back(); + } + + /// Integrates a magnitude value along the cylinder. + /// @param m is the magnitude value to be integrated (this is usually the radius) + T integrate(unsigned m = 0){ + + T M = 0; //initialize the integral to zero + T m0, m1; //allocate space for both magnitudes in a single segment + + //vec3 p0, p1; //allocate space for both points in a single segment + + m0 = mags[0][m]; //initialize the first point and magnitude to the first point in the cylinder + //p0 = pos[0]; + + T len = L[0]; //allocate space for the segment length + + //for every consecutive point in the cylinder + for(unsigned p = 1; p < N; p++){ + + //p1 = pos[p]; //get the position and magnitude for the next point + m1 = mags[p][m]; + + if(p > 1) len = (L[p-1] - L[p-2]); //calculate the segment length using the L array + + //add the average magnitude, weighted by the segment length + M += (m0 + m1)/(T)2.0 * len; + + m0 = m1; //move to the next segment by shifting points + } + return M; //return the integral + } + + /// Averages a magnitude value across the cylinder + /// @param m is the magnitude value to be averaged (this is usually the radius) + T average(unsigned m = 0){ + + //return the average magnitude + return integrate(m) / L.back(); + } + + /// Resamples the cylinder to provide a maximum distance of "spacing" between centerline points. All current + /// centerline points are guaranteed to exist in the new cylinder + /// @param spacing is the maximum spacing allowed between sample points + cylinder resample(T spacing){ + + std::vector< vec3 > result; + + vec3 p0 = e[0].P; //initialize p0 to the first point on the centerline + vec3 p1; + unsigned N = size(); //number of points in the current centerline + + //for each line segment on the centerline + for(unsigned int i = 1; i < N; i++){ + p1 = e[i].P; //get the second point in the line segment + + vec3 v = p1 - p0; //calculate the vector between these two points + T d = v.len(); //calculate the distance between these two points (length of the line segment) + + size_t nsteps = (size_t)std::ceil(d / spacing); //calculate the number of steps to take along the segment to meet the spacing criteria + T stepsize = (T)1.0 / nsteps; //calculate the parametric step size between new centerline points + + //for each step along the line segment + for(unsigned s = 0; s < nsteps; s++){ + T alpha = stepsize * s; //calculate the fraction of the distance along the line segment covered + result.push_back(p0 + alpha * v); //push the point at alpha position along the line segment + } + + p0 = p1; //shift the points to move to the next line segment + } + + result.push_back(e[size() - 1].P); //push the last point in the centerline + + return cylinder(result); + + } + + +}; + +} +#endif diff --git a/tira/visualization/glObj.h b/tira/visualization/glObj.h new file mode 100644 index 0000000..40e7175 --- /dev/null +++ b/tira/visualization/glObj.h @@ -0,0 +1,221 @@ +#ifndef STIM_GLOBJ_H +#define STIM_GLOBJ_H + +//#include +#include +#include +#include +#include + + +namespace stim +{ +/** This class uses the loading functionality of the obj class in order to + * Assist with the visualization the segmented vessels. + * Uses +*/ +template +class glObj : public virtual stim::obj +{ +private: + using stim::obj::L; + using stim::obj::P; + using stim::obj::F; +// using stim::obj::numL(); +// using std::vector::size; +// using std::vector::at; + GLuint dList; + + + void + init() + { + if(glIsList(dList)) + glDeleteLists(dList, 1); + dList = glGenLists(1); + glListBase(dList); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + } + + void + Create(GLenum mode, bool blend = false, float opacity = 1.0) + { + + if(blend) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + int len = (int) stim::obj::numL(); + std::vector< stim::vec > line; + glNewList(dList, GL_COMPILE); + glLineWidth(3.5); + for(int i = 0; i < len; i++){ + line = stim::obj::getL_V(i); + if(mode == GL_SELECT) + { + glLoadName(i); + } + if(blend == false) + { + glColor3f(0.0, 1.0, 0.05); + } + else + { + glColor4f(50.0/256.0,205.0/256.0, 50.0/256.0, opacity); ///Dark Green. + } + //glColor3ub(rand()%255, rand()%255, rand()%255); + glBegin(GL_LINE_STRIP); + for(int j = 0; j < line.size(); j++){ + glVertex3f( + line[j][0], + line[j][1], + line[j][2] + ); + } + glEnd(); + } + if(blend) + glDisable(GL_BLEND); + glEndList(); + CHECK_OPENGL_ERROR + } + + ///Method for drawing selectively opaque things. + void + Create(GLenum mode, stim::vec3 point) + { + float l, opacity; + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + int len = (int) stim::obj::numL(); + std::vector< stim::vec > line; + glNewList(dList, GL_COMPILE); + glLineWidth(3.5); + for(int i = 0; i < len; i++){ + line = stim::obj::getL_V(i); + if(mode == GL_SELECT) + { + glLoadName(i); + } + + glBegin(GL_LINE_STRIP); + for(int j = 0; j < line.size(); j++){ + + l = sqrt(pow((point[0] - line[j][0]),2) + pow((point[1] - line[j][1]),2) + pow((point[2] - line[j][2]),2)); + if(l < 10) + { + opacity = 1.0; + } else if( l > 40) { + opacity = 0.0; + } else { + opacity = l*(-1.0/30.0) + 4.0/3.0; + } + + glColor4f(50.0/256.0,205.0/256.0, 50.0/256.0, opacity); ///Dark Green. + glVertex3f( + line[j][0], + line[j][1], + line[j][2] + ); + } + glEnd(); + } + glDisable(GL_BLEND); + glEndList(); + CHECK_OPENGL_ERROR + } + +public: + glObj() + { + + } + + void + createFromSelf(GLenum mode = GL_RENDER, bool blend = false, float opacity = 1.0) + { + // glPopMatrix(); + init(); + Create(mode, blend, opacity); + // glPushMatrix(); + } + + void + createFromSelf(stim::vec3 point, GLenum mode = GL_RENDER) + { + // glPopMatrix(); + init(); + Create(mode, point); + // glPushMatrix(); + } + + void + createFromFile(std::string filename, GLenum mode = GL_RENDER) + { + stim::obj::load(filename); + glPushMatrix(); //Safety Operation to avoid changing the current matrix. + init(); + Create(mode); + glPopMatrix(); + CHECK_OPENGL_ERROR + } + + + void + Render(bool blend = false) + { + if(blend) + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + glCallList(dList); + if(blend) + glDisable(GL_BLEND); + CHECK_OPENGL_ERROR + } + + void + RenderLine(int i) + { + std::vector < stim::vec > line; + int len = (int) stim::obj::numL(); + line = stim::obj::getL_V(i); + glColor3f(0.5, 1.0, 0.5); + glLineWidth(3.0); + glBegin(GL_LINE_STRIP); + for(int j = 0; j < line.size(); j++){ + glVertex3f( + line[j][0], + line[j][1], + line[j][2] + ); + } + glEnd(); + } + + void + RenderLine(std::vector< stim::vec3 > l) + { + glColor3f(0.5, 1.0, 0.5); + glLineWidth(3.0); + glBegin(GL_LINE_STRIP); + for(int j = 0; j < l.size(); j++){ + glVertex3f( + l[j][0], + l[j][1], + l[j][2] + ); + } + glEnd(); + } + +}; +} +#endif diff --git a/tira/visualization/gl_aaboundingbox.h b/tira/visualization/gl_aaboundingbox.h new file mode 100644 index 0000000..6796446 --- /dev/null +++ b/tira/visualization/gl_aaboundingbox.h @@ -0,0 +1,63 @@ +#ifndef STIM_GL_AABB +#define STIM_GL_AABB + +#include +#include + +namespace stim { + + template + class gl_aaboundingbox : public aaboundingbox { + + public: + + using stim::aaboundingbox::A; + using stim::aaboundingbox::B; + + //default constructor + gl_aaboundingbox() : stim::aaboundingbox() {} + + //constructor takes an AABB + gl_aaboundingbox(stim::aaboundingbox b) : stim::aaboundingbox(b) {} + + + /// Specifies vertices of the bounding box using CW winding. Use GL_LINE_LOOP for wireframe or GL_QUADS for a solid. + void glWire() { + + //front plane (in A[2]) + glBegin(GL_LINE_LOOP); + glVertex3f(A[0], A[1], A[2]); + glVertex3f(A[0], B[1], A[2]); + glVertex3f(B[0], B[1], A[2]); + glVertex3f(B[0], A[1], A[2]); + glEnd(); + + //back plane (in B[2]) + glBegin(GL_LINE_LOOP); + glVertex3f(B[0], B[1], B[2]); + glVertex3f(A[0], B[1], B[2]); + glVertex3f(A[0], A[1], B[2]); + glVertex3f(B[0], A[1], B[2]); + glEnd(); + + //fill out the rest of the lines to connect the two faces + glBegin(GL_LINES); + glVertex3f(A[0], B[1], A[2]); + glVertex3f(A[0], B[1], B[2]); + glVertex3f(B[0], B[1], B[2]); + glVertex3f(B[0], B[1], A[2]); + glVertex3f(B[0], A[1], A[2]); + glVertex3f(B[0], A[1], B[2]); + glVertex3f(A[0], A[1], B[2]); + glVertex3f(A[0], A[1], A[2]); + glEnd(); + + } + + + }; //end stim::gl_aabb + + +}; //end namespace stim + +#endif diff --git a/tira/visualization/gl_network.h b/tira/visualization/gl_network.h new file mode 100644 index 0000000..89ff481 --- /dev/null +++ b/tira/visualization/gl_network.h @@ -0,0 +1,224 @@ +#ifndef JACK_GL_NETWORK +#define JACK_GL_NETWORK + +#include +#include +#include + +namespace jack { + + template + class gl_network : public jack::network { + private: + std::vector ecolor; // colormap for edges + std::vector vcolor; // colormap for vertices + GLint subdivision; // rendering subdivision + + /// basic geometry rendering + // main sphere rendering function + void sphere(T x, T y, T z, T radius) { + glPushMatrix(); + glTranslatef((GLfloat)x, (GLfloat)y, (GLfloat)z); + glutSolidSphere((double)radius, subdivision, subdivision); + glPopMatrix(); + } + // rendering sphere using glut function + void draw_sphere(T x, T y, T z, T radius, T alpha = 1.0f) { + if (alpha != 1.0f) { + glEnable(GL_BLEND); // enable color blend + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // set blend function + glDisable(GL_DEPTH_TEST); // disable depth buffer + glColor4f(vcolor[0], vcolor[1], vcolor[2], alpha); // set color + sphere(x, y, z, radius, subdivision); + glDisable(GL_BLEND); // disable color blend + glEnable(GL_DEPTH_TEST); // enbale depth buffer again + } + else { + glColor3f(vcolor[0], vcolor[1], vcolor[2]); + sphere(x, y, z, radius, subdivision); + } + } + // rendering sphere using quads + void draw_sphere(T x, T y, T z, T radius, T alpha = 1.0f) { + GLint stack = subdivision; + GLint slice = subdivision; + + if (alpha != 1.0f) { + glEnable(GL_BLEND); // enable color blend + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // set blend function + glDisable(GL_DEPTH_TEST); // disable depth buffer + } + glColor4f(vcolor[0], vcolor[1], vcolor[2], alpha); // set color + + T step_z = stim::PI / slice; // step angle along z-axis + T step_xy = 2 * stim::PI / stack; // step angle in xy-plane + T xx[4], yy[4], zz[4]; // store coordinates + + T angle_z = 0.0; // start angle + T angle_xy = 0.0; + + glBegin(GL_QUADS); + for (unsigned i = 0; i < slice; i++) { // around the z-axis + angle_z = i * step_z; // step step_z each time + + for (unsigned j = 0; j < stack; j++) { // along the z-axis + angle_xy = j * step_xy; // step step_xy each time, draw floor by floor + + xx[0] = radius * std::sin(angle_z) * std::cos(angle_xy); // four vertices + yy[0] = radius * std::sin(angle_z) * std::sin(angle_xy); + zz[0] = radius * std::cos(angle_z); + + xx[1] = radius * std::sin(angle_z + step_z) * std::cos(angle_xy); + yy[1] = radius * std::sin(angle_z + step_z) * std::sin(angle_xy); + zz[1] = radius * std::cos(angle_z + step_z); + + xx[2] = radius * std::sin(angle_z + step_z) * std::cos(angle_xy + step_xy); + yy[2] = radius * std::sin(angle_z + step_z) * std::sin(angle_xy + step_xy); + zz[2] = radius * std::cos(angle_z + step_z); + + xx[3] = radius * std::sin(angle_z) * std::cos(angle_xy + step_xy); + yy[3] = radius * std::sin(angle_z) * std::sin(angle_xy + step_xy); + zz[3] = radius * std::cos(angle_z); + + for (unsigned k = 0; k < 4; k++) { + glVertex3f(x + xx[k], y + yy[k], z + zz[k]); // draw the floor plane + } + } + } + glEnd(); + + if (alpha != 1.0f) { + glDisable(GL_BLEND); // disable color blend + glEnable(GL_DEPTH_TEST); // enbale depth buffer again + } + } + + // render edge as cylinders + void draw_cylinder(T scale = 1.0f) { + stim::circle C1, C2; // temp circles + std::vector > Cp1, Cp2; // temp lists for storing points on those circles + T r1, r2; // temp radii + + size_t num_edge = E.size(); + size_t num_vertex = V.size(); + + for (size_t i = 0; i < num_edge; i++) { // for every edge + glColor3f(ecolor[i * 3 + 0], ecolor[i * 3 + 1], ecolor[i * 3 + 2]); + for (size_t j = 1; j < num_vertex; j++) { // for every vertex except first one + C1 = E[i].circ(j - 1); // get the first circle plane of this segment + C2 = E[i].circ(j); // get the second circle plane of this segment + r1 = E[i].r(j - 1); // get the radius at first point + r2 = E[i].r(j); // get the radius at second point + C1.set_R(scale * r1); // rescale + C2.set_R(scale * r2); + Cp1 = C1.glpoints((unsigned)subdivision); // get 20 points on first circle plane + Cp2 = C2.glpoints((unsigned)subdivision); // get 20 points on second circle plane + + glBegin(GL_QUAD_STRIP); + for (size_t k = 0; k < subdivision + 1; k++) { + glVertex3f(Cp1[k][0], Cp1[k][1], Cp1[k][2]); + glVertex3f(Cp2[k][0], Cp2[k][1], Cp2[k][2]); + } + glEnd(); + } + } + } + + + protected: + using jack::network::E; + using jack::network::T; + + GLuint dlist; + + public: + + /// constructors + // empty constructor + gl_network() : jack::network() { + dlist = 0; + subdivision = 20; + } + + gl_network(jack::network N) : jack::network(N) { + dlist = 0; + subdivision = 20; + ecolor.resize(N.edges * 3, 0.0f); // default black color + vcolor.resize(N.vertices * 3, 0.0f); + } + + // compute the smallest bounding box of current network + aabboundingbox boundingbox() { + aaboundingbox bb; // create a bounding box object + + for (size_t i = 0; i < E.size(); i++) // for every edge + for (size_t j = 0; j < E[i].size(); j++) // for every point on that edge + bb.expand(E[i][j]); // expand the bounding box to include that point + + return bb; + } + + // change subdivision + void change_subdivision(GLint value) { + subdivision = value; + } + + + /// rendering functions + // render centerline + void centerline() { + size_t num = E.size(); // get the number of edges + for (size_t i = 0; i < num; i++) { + glColor3f(colormap[i * 3 + 0], colormap[i * 3 + 1], colormap[i * 3 + 2]); + glBegin(GL_LINE_STRIP); + for (size_t j = 0; j < E[i].size(); j++) + glVertex3f(E[i][j][0], E[i][j][1], E[i][j][2]); + glEnd(); + } + } + + + // render network as bunch of spheres and cylinders + void network(T scale = 1.0f) { + stim::vec3 v1, v2; // temp vertices for rendering + T r1, r2; // temp radii for rendering + + if (!glIsList(dlist)) { // if dlist is not a display list, create one + dlist = glGenLists(1); // generate a display list + glNewList(dlist, GL_COMPILE); // start a new display list + + // render every vertex as a sphere + size_t num = V.size(); + for (size_t i = 0; i < num; i++) { + v1 = stim::vec3(V[i][0], V[i][1], V[i][2]); // get the vertex for rendering + r1 = (*this).network::r(i); + draw_sphere(v1[0], v1[1], v1[2], r1 * scale); + } + + // render every edge as a cylinder + draw_cylinder(scale); + + glEndList(); // end the display list + } + glCallList(dlist); + } + }; +} + + + + + + + + + + + + + + + + + +#endif \ No newline at end of file diff --git a/tira/visualization/gl_spharmonics-dep.h b/tira/visualization/gl_spharmonics-dep.h new file mode 100644 index 0000000..37f2414 --- /dev/null +++ b/tira/visualization/gl_spharmonics-dep.h @@ -0,0 +1,259 @@ +#ifndef STIM_GL_SPHARMONICS_H +#define STIM_GL_SPHARMONICS_H + +#include + +#include +#include +#include + +namespace stim{ + + +template +class gl_spharmonics : public spharmonics{ + +protected: + + using stim::spharmonics::SH; + stim::spharmonics surface; //harmonic that stores the surface information + stim::spharmonics color; //harmonic that stores the color information + T* func_surface; //stores the raw function data (samples at each point) + T* func_color; //stores the raw color data (samples at each point) + GLuint color_tex; //texture map that acts as a colormap for the spherical function + unsigned int N; //resolution of the spherical grid + + ///generates the + void gen_surface(){ + //allocate memory and initialize the function to zero + func_surface = (T*) malloc(N * N * sizeof(T)); + memset(func_surface, 0, sizeof(T) * N * N); + + double theta, phi; + double result; + int l, m; + + + l = m = 0; + //for each coefficient + for(unsigned int c = 0; c < surface.C.size(); c++){ + + //iterate through the entire 2D grid representing the function + for(unsigned int xi = 0; xi < N; xi++){ + for(unsigned int yi = 0; yi < N; yi++){ + + //get the spherical coordinates for each grid point + theta = (2 * PI) * ((double)xi / (N-1)); + phi = PI * ((double)yi / (N-1)); + + //sum the contribution of the current spherical harmonic based on the coefficient + result = surface.C[c] * SH(l, m, theta, phi); + + //store the result in a 2D array (which will later be used as a texture map) + func_surface[yi * N + xi] += result; + } + } + + //keep track of m and l here + m++; //increment m + //if we're in a new tier, increment l and set m = -l + if(m > l){ + l++; + m = -l; + } + } + } + + ///generates the color function + void gen_color(){ + + //initialize the function to zero + func_color = (T*) malloc(N * N * sizeof(T)); + memset(func_color, 0, sizeof(T) * N * N); + + double theta, phi; + double result; + int l, m; + + + l = m = 0; + //for each coefficient + for(unsigned int c = 0; c < color.C.size(); c++){ + + //iterate through the entire 2D grid representing the function + for(unsigned int xi = 0; xi < N; xi++){ + for(unsigned int yi = 0; yi < N; yi++){ + + //get the spherical coordinates for each grid point + theta = (2 * PI) * ((double)xi / (N-1)); + phi = PI * ((double)yi / (N-1)); + + //sum the contribution of the current spherical harmonic based on the coefficient + result = color.C[c] * SH(l, m, theta, phi); + + //store the result in a 2D array (which will later be used as a texture map) + func_color[yi * N + xi] += result; + } + } + + //keep track of m and l here + m++; //increment m + //if we're in a new tier, increment l and set m = -l + if(m > l){ + l++; + m = -l; + } + } + } + + void gl_prep_draw(){ + + //enable depth testing + //this has to be used instead of culling because the sphere can have negative values + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glEnable(GL_TEXTURE_2D); //enable 2D texture mapping + } + + //draw a texture mapped sphere representing the function surface + void gl_draw_sphere() { + + //bind the 2D texture representing the color map + glBindTexture(GL_TEXTURE_2D, color_tex); + + //Draw the Sphere + int i, j; + + for(i = 1; i <= N-1; i++) { + double phi0 = PI * ((double) (i - 1) / (N-1)); + double phi1 = PI * ((double) i / (N-1)); + + glBegin(GL_QUAD_STRIP); + for(j = 0; j <= N; j++) { + + //calculate the indices into the function array + int phi0_i = i-1; + int phi1_i = i; + int theta_i = j; + if(theta_i == N) + theta_i = 0; + + double v0 = func_surface[phi0_i * N + theta_i]; + double v1 = func_surface[phi1_i * N + theta_i]; + + v0 = fabs(v0); + v1 = fabs(v1); + + + double theta = 2 * PI * (double) (j - 1) / N; + double x0 = v0 * cos(theta) * sin(phi0); + double y0 = v0 * sin(theta) * sin(phi0); + double z0 = v0 * cos(phi0); + + double x1 = v1 * cos(theta) * sin(phi1); + double y1 = v1 * sin(theta) * sin(phi1); + double z1 = v1 * cos(phi1); + + glTexCoord2f(theta / (2 * PI), phi0 / PI); + glVertex3f(x0, y0, z0); + + glTexCoord2f(theta / (2 * PI), phi1 / PI); + glVertex3f(x1, y1, z1); + } + glEnd(); + } + + glBindTexture(GL_TEXTURE_2D, 0); + } + + void gen_texture() + { + //allocate space for the color map + unsigned int bytes = N * N * sizeof(unsigned char) * 3; + unsigned char* color_image; + color_image = (unsigned char*) malloc(bytes); + + //generate a colormap from the function + stim::cpu2cpu(func_color, color_image, N*N, stim::cmBrewer); + + //prep everything for drawing + gl_prep_draw(); + + //generate an OpenGL texture map in the current context + glGenTextures(1, &color_tex); + //bind the texture + glBindTexture(GL_TEXTURE_2D, color_tex); + + //copy the color data from the buffer to the GPU + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, N, N, 0, GL_RGB, GL_UNSIGNED_BYTE, color_image); + + //initialize all of the texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + //free the buffer + free(color_image); + } + + void gl_init(unsigned int n){ + + //set the sphere resolution + N = n; + + //set up the surface data. + gen_surface(); + + //set up the color data and generate the corresponding texture. + gen_color(); + gen_texture(); + + } + +public: + gl_spharmonics(unsigned int n = 256) { + gl_init(n); + } + + gl_spharmonics( stim::spharmonics in_function, unsigned int n = 256) + { + surface = in_function; + color = in_function; + gl_init(n); + } + + gl_spharmonics( stim::spharmonics in_function, stim::spharmonics in_color, unsigned int n = 256) + { + surface = in_function; + color = in_color; + gl_init(n); + } + + gl_spharmonics& operator=(const spharmonics rhs) { + gl_spharmonics result(rhs.C.size()); + result.C = spharmonics::rhs.C; + return result; + } + + void glRender(){ + //set all OpenGL parameters required for drawing + gl_prep_draw(); + + //draw the sphere + gl_draw_sphere(); + } + + void glInit(unsigned int n = 256){ + gl_init(n); + } +}; //end gl_spharmonics + + +}; //end namespace stim + + + + +#endif diff --git a/tira/visualization/gl_spharmonics.h b/tira/visualization/gl_spharmonics.h new file mode 100644 index 0000000..3f5bc8f --- /dev/null +++ b/tira/visualization/gl_spharmonics.h @@ -0,0 +1,299 @@ +#ifndef STIM_GL_SPHARMONICS_H +#define STIM_GL_SPHARMONICS_H + +#include +#include +#include +#include +#include + +namespace stim { + + template + class gl_spharmonics { + + GLuint dlist; + GLuint tex; + + bool displacement; + bool colormap; + bool magnitude; + + void init_tex() { + T* sfunc = (T*)malloc(N * N * sizeof(T)); //create a 2D array to store the spherical function + Sc.get_func(sfunc, N, N); //generate the spherical function based on the Sc coefficients + unsigned char* tex_buffer = (unsigned char*)malloc(3 * N * N); //create a buffer to store the texture map + stim::cpu2cpu(sfunc, tex_buffer, N * N, stim::cmBrewer); //create a Brewer colormap based on the spherical function + stim::buffer2image(tex_buffer, "sfunc.ppm", N, N); + + if (tex) glDeleteTextures(1, &tex); //if a texture already exists, delete it + glGenTextures(1, &tex); //create a new texture and store the ID + glBindTexture(GL_TEXTURE_2D, tex); //bind the texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, N, N, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_buffer); //copy the color data from the buffer to the GPU + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //initialize all of the texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + } + + public: + stim::spharmonics Sc; //spherical harmonic representing the color component + stim::spharmonics Sd; //spherical harmonic representing the displacement component + size_t N; + + gl_spharmonics(size_t slices) { + N = slices; + dlist = 0; //initialize the display list index to zero (no list) + tex = 0; //initialize the texture index to zero (no texture) + displacement = true; + colormap = true; + magnitude = true; + } + + gl_spharmonics(stim::spharmonics disp, stim::spharmonics color, size_t slices) : + gl_spharmonics(slices) + { + Sc = color; + Sd = disp; + } + + ~gl_spharmonics() { + if (dlist) glDeleteLists(dlist, 1); //delete the display list when the object is destroyed + } + + ///saves the coeffients of a spherical harmonics in a text documents. + void save_coeffs(std::string filename) + { + std::ofstream myfile; + myfile.open(filename.c_str()); + for(int i = 0; i < Sd.C.size()-1; i++) + { + myfile << Sd.C[i] << ","; + } + myfile << Sd.C[Sd.C.size()-1] << std::endl; + for(int i = 0; i < Sc.C.size()-1; i++) + { + myfile << Sc.C[i] << ","; + } + myfile << Sc.C[Sc.C.size()-1] << std::endl; + myfile.close(); + } + + void save(std::string filename) + { + + if (!tex) { + init_tex(); + } + + stim::obj object; + size_t theta_i, phi_i; + T d_theta = (T)stim::TAU / (T)N; + T d_phi = (T)stim::PI / (T)(N-1); + object.matKd("sfunc.jpg"); + for (phi_i = 1; phi_i < N; phi_i++) { + T phi = phi_i * d_phi; + object.Begin(OBJ_TRIANGLE_STRIP); + for (theta_i = 0; theta_i <= N; theta_i++) { + T theta = (N - theta_i) * d_theta; + float theta_t = 1 - (float)theta_i / (float)N; + + T r; + if (!displacement) r = 1; //if no displacement, set the r value to 1 (renders a unit sphere) + else r = Sd(theta, phi); //otherwise calculate the displacement value + + glColor3f(1.0f, 1.0f, 1.0f); + if (!colormap) { //if no colormap is being rendered + if (r < 0) glColor3f(1.0, 0.0, 0.0); //if r is negative, render it red + else glColor3f(0.0, 1.0, 0.0); //otherwise render in green + } + if (magnitude) { //if the magnitude is being displaced, calculate the magnitude of r + if (r < 0) r = -r; + } + stim::vec3 s(r, theta, phi); + stim::vec3 c = s.sph2cart(); + stim::vec3 n; //allocate a value to store the normal + if (!displacement) n = c; //if there is no displacement, the normal is spherical + else n = Sd.dphi(theta, phi).cross(Sd.dtheta(theta, phi)); //otherwise calculate the normal as the cross product of derivatives + + object.TexCoord(theta_t, (float)phi_i / (float)N); + //std::cout << theta_t <<" "<<(float)phi_i / (float)N << "----------------"; + object.Normal(n[0], n[1], n[2]); + object.Vertex(c[0], c[1], c[2]); + + T r1; + if (!displacement) r1 = 1; + else r1 = Sd(theta, phi - d_phi); + if (!colormap) { //if no colormap is being rendered + if (r1 < 0) glColor3f(1.0, 0.0, 0.0); //if r1 is negative, render it red + else glColor3f(0.0, 1.0, 0.0); //otherwise render in green + } + if (magnitude) { //if the magnitude is being rendered, calculate the magnitude of r + if (r1 < 0) r1 = -r1; + } + stim::vec3 s1(r1, theta, phi - d_phi); + stim::vec3 c1 = s1.sph2cart(); + stim::vec3 n1; + if (!displacement) n1 = c1; + else n1 = Sd.dphi(theta, phi - d_phi).cross(Sd.dtheta(theta, phi - d_phi)); + + //std::cout << theta_t << " " << (float)(phi_i - 1) / (float)N << std::endl; + object.TexCoord(theta_t, 1.0/(2*(N)) + (float)(phi_i-1) / (float)N); + object.Normal(n1[0], n1[1], n1[2]); + object.Vertex(c1[0], c1[1], c1[2]); + } + object.End(); + } + object.matKd(); + object.save(filename); + } + + + + /// This function renders the spherical harmonic to the current OpenGL context + void render() { + //glShadeModel(GL_FLAT); + glPushAttrib(GL_ENABLE_BIT); + glDisable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + + if (!tex) { + init_tex(); + } + + if (colormap) { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tex); + } + + if (!dlist) { + dlist = glGenLists(1); + glNewList(dlist, GL_COMPILE); + glPushAttrib(GL_ENABLE_BIT); + glEnable(GL_NORMALIZE); + + //Draw the Sphere + size_t theta_i, phi_i; + T d_theta = (T)stim::TAU / (T)N; + T d_phi = (T)stim::PI / (T)(N-1); + + for (phi_i = 1; phi_i < N; phi_i++) { + T phi = phi_i * d_phi; + glBegin(GL_QUAD_STRIP); + for (theta_i = 0; theta_i <= N; theta_i++) { + T theta = (N - theta_i) * d_theta; + float theta_t = 1 - (float)theta_i / (float)N; + + T r; + if (!displacement) r = 1; //if no displacement, set the r value to 1 (renders a unit sphere) + else r = Sd(theta, phi); //otherwise calculate the displacement value + + glColor3f(1.0f, 1.0f, 1.0f); + if (!colormap) { //if no colormap is being rendered + if (r < 0) glColor3f(1.0, 0.0, 0.0); //if r is negative, render it red + else glColor3f(0.0, 1.0, 0.0); //otherwise render in green + } + if (magnitude) { //if the magnitude is being displaced, calculate the magnitude of r + if (r < 0) r = -r; + } + stim::vec3 s(r, theta, phi); + stim::vec3 c = s.sph2cart(); + stim::vec3 n; //allocate a value to store the normal + if (!displacement) n = c; //if there is no displacement, the normal is spherical + else n = Sd.dphi(theta, phi).cross(Sd.dtheta(theta, phi)); //otherwise calculate the normal as the cross product of derivatives + + glTexCoord2f(theta_t, (float)phi_i / (float)N); + //std::cout << theta_t <<" "<<(float)phi_i / (float)N << "----------------"; + glNormal3f(n[0], n[1], n[2]); + glVertex3f(c[0], c[1], c[2]); + + T r1; + if (!displacement) r1 = 1; + else r1 = Sd(theta, phi - d_phi); + if (!colormap) { //if no colormap is being rendered + if (r1 < 0) glColor3f(1.0, 0.0, 0.0); //if r1 is negative, render it red + else glColor3f(0.0, 1.0, 0.0); //otherwise render in green + } + if (magnitude) { //if the magnitude is being rendered, calculate the magnitude of r + if (r1 < 0) r1 = -r1; + } + stim::vec3 s1(r1, theta, phi - d_phi); + stim::vec3 c1 = s1.sph2cart(); + stim::vec3 n1; + if (!displacement) n1 = c1; + else n1 = Sd.dphi(theta, phi - d_phi).cross(Sd.dtheta(theta, phi - d_phi)); + + //std::cout << theta_t << " " << (float)(phi_i - 1) / (float)N << std::endl; + glTexCoord2f(theta_t, 1.0/(2*(N)) + (float)(phi_i-1) / (float)N); + glNormal3f(n1[0], n1[1], n1[2]); + glVertex3f(c1[0], c1[1], c1[2]); + } + glEnd(); + } + glPopAttrib(); + glEndList(); + } + glCallList(dlist); //call the display list to render + + glPopAttrib(); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } + + /// Push a coefficient to the spherical harmonic - by default, push applies the component to both the displacement and color SH + void push(T coeff) { + Sd.push(coeff); + Sc.push(coeff); + } + + /// Resize the spherical harmonic coefficient array + void resize(size_t s) { + Sd.resize(s); + Sc.resize(s); + } + + /// Set a spherical harmonic coefficient to the given value + void setc(unsigned int c, T value) { + Sd.setc(c, value); + Sc.setc(c, value); + } + + void project(T* data, size_t x, size_t y, size_t nc) { + Sd.project(data, x, y, nc); + Sc = Sd; + } + + /// Project a set of samples onto the basis + void project(std::vector >& vlist, size_t nc) { + Sd.project(vlist, nc); + Sc = Sd; + } + + /// Calculate a density function from a list of points in spherical coordinates + void pdf(std::vector >& vlist, size_t nc) { + Sd.pdf(vlist, nc); + Sc = Sd; + } + + void slices(size_t s) { + N = s; + } + + size_t slices() { + return N; + } + + void rendermode(bool displace, bool color, bool mag = true) { + displacement = displace; + colormap = color; + magnitude = mag; + } + + }; +} + + + +#endif diff --git a/tira/visualization/glnetwork-dep.h b/tira/visualization/glnetwork-dep.h new file mode 100644 index 0000000..26813c5 --- /dev/null +++ b/tira/visualization/glnetwork-dep.h @@ -0,0 +1,177 @@ +#ifndef STIM_GLNETWORK_H +#define STIM_GLNETWORK_H + +#include +#include +#include "network.h" +#include +#include +#include +#include +#include "fiber.h" + + +namespace stim +{ +template +class glnetwork : public virtual network +{ +private: + using stim::network::E; + + GLuint dList; ///displaylist for the lines. + GLuint cList; ///displaylist for the cylinders. + + void init() + { + ///clear lists if there is data in them. + ///adding points may create errors or uncessary duplicate points. + if(glIsList(dList)) + glDeleteLists(dList, 1); + if(glIsList(cList)) + glDeleteLists(cList, 1); + dList = glGenLists(1); ///create the lists + cList = glGenLists(1); + + ///set up the Line list. + glListBase(dList); + glMatrixMode(GL_PROJECTION); + glLoadIdentity; + glMatrixMode(GL_MODELVIEW); + glLoadIdentity; + + ///set up the cylinder List. + glListBase(cList); + glMatrixMode(GL_PROJECTION); + glLoadIdentity; + glMatrixMode(GL_MODELVIEW); + glLoadIdentity; + } + + void + Create(GLenum mode, int sides = 8) + { + glListBase(dList); + glNewList(dList, GL_COMPILE); + glLineWidth(3.5); + for(int i = 0; i < E.size(); i++) + { + if(mode == GL_SELECT) + { +// glLineWidth(3.5); + glLoadName(i); + } + else{ +// glLineWidth(1.0+1.0*i); + } + glColor3f(0.0, 1.0-0.05*i, i*0.05); + std::vector > line = getEdgeCenterLine(i); + glBegin(GL_LINE_STRIP); + for(int j = 0; j < line.size(); j++) + { + glVertex3f(line[j][0], + line[j][1], + line[j][2]); + } + glEnd(); + } + glEndList(); + + glListBase(cList); + glNewList(cList, GL_COMPILE); + + for(int i = 0; i < E.size(); i++) + { + if(mode == GL_SELECT) + { + glLoadName(i); + } + glColor3f(1.0, 1.0, 0.0); + std::vector > line = getEdgeCenterLine(i); + std::vector > linemag = getEdgeCenterLineMag(i); + stim::cylinder cyl(line, linemag); + std::vector > > p = cyl.getPoints(sides); + for(int i = 0; i < p.size()-1; i++) + { + for(int j = 0; j < p[0].size()-1; j++) + { + glColor4f(1.0, 1.0, 0.0, 0.5); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glVertex3f(p[i][j][0], p[i][j][1], p[i][j][2]); + glVertex3f(p[i][j+1][0], p[i][j+1][1], p[i][j+1][2]); + glVertex3f(p[i+1][j+1][0], p[i+1][j+1][1], p[i+1][j+1][2] ); + glVertex3f(p[i+1][j][0], p[i+1][j][1], p[i+1][j][2]); + glEnd(); + glDisable(GL_BLEND); + + glColor4f(1.0, 0.0, 1.0, 1.0); + glLineWidth(2.0); + glBegin(GL_LINES); + glVertex3f(p[i][j][0], p[i][j][1], p[i][j][2]); + glVertex3f(p[i][j+1][0], p[i][j+1][1], p[i][j+1][2]); + glVertex3f(p[i][j][0], p[i][j][1], p[i][j][2]); + glVertex3f(p[i+1][j][0], p[i+1][j][1], p[i+1][j][2] ); + glEnd(); + } + + } + + + + } + glEndList(); +// CHECK_OPENGL_ERROR + } + +public: + using stim::network::sizeE; + using stim::network::getEdgeCenterLine; + using stim::network::getEdgeCenterLineMag; + glnetwork() + { + + } + + void + createFromSelf(GLenum mode = GL_RENDER, int sides = 8) + { + init(); + Create(mode, sides); + } + + void + Render() + { + glCallList(dList); +// CHECK_OPENGL_ERROR + } + + void + RenderCylinders() + { + glCallList(cList); +// CHECK_OPENGL_ERROR + } + + void + RenderLine(std::vector > l) + { + glColor3f(0.5, 1.0, 0.5); + glLineWidth(3.0); + glBegin(GL_LINE_STRIP); + for(int j = 0; j < l.size(); j++){ + glVertex3f( + l[j][0], + l[j][1], + l[j][2] + ); + } + glEnd(); + } + +}; +} + +#endif diff --git a/tira/visualization/glut_template.cpp b/tira/visualization/glut_template.cpp new file mode 100644 index 0000000..784bc18 --- /dev/null +++ b/tira/visualization/glut_template.cpp @@ -0,0 +1,170 @@ +/* Don't change this file. Make a copy in your project and change parameters as necessary. + + In order to use this template, specify the following signature in your main.cpp file: + void glut_init(int argc, char* argv[]); + +*/ + +#ifndef GLUT_TEMPLATE_H +#define GLUT_TEMPLATE_H + +#include +#include + +void render_scene(); + +void draw_colorcube() { + glBegin(GL_LINES); //start a line series + glColor3f(0, 0, 0); + glVertex3f(-0.5, -0.5, -0.5); + glColor3f(1, 0, 0); + glVertex3f(0.5, -0.5, -0.5); + + glColor3f(0, 0, 0); + glVertex3f(-0.5, -0.5, -0.5); + glColor3f(0, 1, 0); + glVertex3f(-0.5, 0.5, -0.5); + + glColor3f(0, 0, 0); + glVertex3f(-0.5, -0.5, -0.5); + glColor3f(0, 0, 1); + glVertex3f(-0.5, -0.5, 0.5); + + glColor3f(1, 1, 1); + glVertex3f(0.5, 0.5, 0.5); + glColor3f(1, 1, 0); + glVertex3f(0.5, 0.5, -0.5); + + glColor3f(1, 1, 1); + glVertex3f(0.5, 0.5, 0.5); + glColor3f(1, 0, 1); + glVertex3f(0.5, -0.5, 0.5); + + glColor3f(1, 1, 1); + glVertex3f(0.5, 0.5, 0.5); + glColor3f(0, 1, 1); + glVertex3f(-0.5, 0.5, 0.5); + + glColor3f(1, 0, 0); + glVertex3f(0.5, -0.5, -0.5); + glColor3f(1, 1, 0); + glVertex3f(0.5, 0.5, -0.5); + + glColor3f(1, 0, 0); + glVertex3f(0.5, -0.5, -0.5); + glColor3f(1, 0, 1); + glVertex3f(0.5, -0.5, 0.5); + + glColor3f(0, 1, 0); + glVertex3f(-0.5, 0.5, -0.5); + glColor3f(1, 1, 0); + glVertex3f(0.5, 0.5, -0.5); + + glColor3f(0, 1, 0); + glVertex3f(-0.5, 0.5, -0.5); + glColor3f(0, 1, 1); + glVertex3f(-0.5, 0.5, 0.5); + + glColor3f(0, 0, 1); + glVertex3f(-0.5, -0.5, 0.5); + glColor3f(1, 0, 1); + glVertex3f(0.5, -0.5, 0.5); + + glColor3f(0, 0, 1); + glVertex3f(-0.5, -0.5, 0.5); + glColor3f(0, 1, 1); + glVertex3f(-0.5, 0.5, 0.5); + glEnd(); +}} + +struct glut_template_struct { + float theta_scale = 0.01f; + float phi_scale = 0.01f; + float zoom_scale = 0.1f; + stim::camera cam; //create a camera object + int mx, my; //mouse coordinates in the window space + float d = 1.5; //initial distance between the camera and the sphere + bool rotate_zoom = true; //sets the current camera mode (rotation = true, zoom = false) + + bool axis = false; //render the z-axis (set via a command line flag) +} gt; + +//render the XYZ axes +void render_axes() { + glDisable(GL_TEXTURE_2D); //turn off texture mapping + glBegin(GL_LINES); + glColor3f(1.0f, 0.0f, 0.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(100.0, 0.0, 0.0); + + glColor3f(0.0f, 1.0f, 0.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 100.0, 0.0); + + glColor3f(0.0f, 0.0f, 1.0f); //set the color to RED and render X + glVertex3f(0.0, 0.0, 0.0); + glVertex3f(0.0, 0.0, 100.0); + glEnd(); +} + +//display function executed to update every frame +void glut_display() { + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); //clear the screen + + glMatrixMode(GL_PROJECTION); //put the projection matrix on the stack + glLoadIdentity(); //set it to the identity matrix + gluPerspective(gt.cam.getFOV(), 1, 0.001, 1000000); //set up a perspective projection + glMatrixMode(GL_MODELVIEW); //load the model view matrix to the stack + glLoadIdentity(); //set it to the identity matrix + stim::vec3 p = gt.cam.getPosition(); //get the camera parameters + stim::vec3 u = gt.cam.getUp(); + stim::vec3 c = gt.cam.getLookAt(); + gluLookAt(p[0], p[1], p[2], c[0], c[1], c[2], u[0], u[1], u[2]); //specify the camera parameters to OpenGL + + render_axes(); //render the axes if the user requests them + draw_colorcube(); + glutSwapBuffers(); //swap in the back buffer (double-buffering is used to prevent tearing) +} + +//process mouse press events +void glut_mouse_press(int button, int state, int x, int y) { + if (button == GLUT_LEFT_BUTTON) //set the camera motion mode based on the mouse button pressed + gt.rotate_zoom = true; + else if (button == GLUT_RIGHT_BUTTON) + gt.rotate_zoom = false; + if (state == GLUT_DOWN) { //if the mouse is pressed + gt.mx = x; gt.my = y; //set the current mouse position + } +} + +//process mouse drags to update the camera +void glut_mouse_drag(int x, int y) { + if (gt.rotate_zoom == true) { //if the camera is in rotation mode, rotate + float theta = gt.theta_scale * (gt.mx - x); + float phi = -gt.phi_scale * (gt.my - y); + gt.cam.OrbitFocus(theta, phi); //if the mouse is dragged + } + else { //otherwize zoom + gt.cam.Push(gt.zoom_scale*(gt.my - y)); + } + gt.mx = x; gt.my = y; //update the mouse position + glutPostRedisplay(); +} + +void glut_init(int argc, char* argv[]) { + glutInit(&argc, argv); //initialize GLUT + glutInitWindowSize(500, 500); //set the size of the GLUT window + glutInitDisplayMode(GLUT_DEPTH | GLUT_RGBA | GLUT_DOUBLE); + glutCreateWindow("3D Tensor Visualization"); //create the GLUT window (and an OpenGL context) + glutDisplayFunc(glut_display); //set the display function (which will be called repeatedly by glutMainLoop) + glutMouseFunc(glut_mouse_press); //set the mouse press function (called when a mouse button is pressed) + glutMotionFunc(glut_mouse_drag); //set the mouse motion function (which will be called any time the mouse is dragged) + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //set the clear color to white + gt.cam.setPosition(gt.d, gt.d, gt.d); //initialize the camera + gt.cam.LookAt(0, 0, 0, 0, 1, 1); + gt.cam.setFOV(40); + glutMainLoop(); //enter the main loop +} + +#endif \ No newline at end of file diff --git a/tira/visualization/obj.h b/tira/visualization/obj.h new file mode 100644 index 0000000..c193192 --- /dev/null +++ b/tira/visualization/obj.h @@ -0,0 +1,1048 @@ +#ifndef STIM_OBJ_H +#define STIM_OBJ_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace stim{ + +/** This class provides an interface for loading, saving, and working with Wavefront OBJ files. + * The class structure uses similar terminology to the OBJ file structure, so one familiar with it + * should be able to work well with this class. + * + * This class currently provides the ability to load explicitly defined geometric objects, including point lists, + * lines, and faces with positions, texture coordinates, and normals. This data is stored in a protected series + * of lists that are accessible to classes that extend this one. + * + * Multiple helper classes are used to facilitate loading, storing, and saving geometry. These are protected + * and therefore only designed to be used within the confines of the class. These helper classes include: + * + * vertex class - contains methods for loading, saving, and storing 1, 2, 3, and 4 component vectors + * triplet class - contains methods for loading, saving, and storing indexed vertices used for geometric objects + * geometry class - contains a list of triplets used to define a geometric structure, such as a face or line + */ + +enum obj_type { OBJ_NONE, OBJ_LINE, OBJ_FACE, OBJ_POINTS, OBJ_TRIANGLE_STRIP }; + +template +class obj{ + +protected: + + enum token_type { OBJ_INVALID, OBJ_V, OBJ_VT, OBJ_VN, OBJ_P, OBJ_L, OBJ_F }; + + struct vertex : public stim::vec{ + + using vec::push_back; + using vec::size; + using vec::at; + using vec::resize; + + //basic constructors (call the stim::vector constructors) + vertex(){} + vertex(T x){ + resize(1); + at(0) = x; + } + vertex(T x, T y) : stim::vec(x, y){} + vertex(T x, T y, T z) : stim::vec(x, y, z){} + vertex(T x, T y, T z, T w) : stim::vec(x, y, z, w){} + + vertex(stim::vec rhs) : stim::vec(rhs){} + + //constructor creates a vertex from a line string + vertex(std::string line){ + + //create a temporary vector used to store string tokens + std::vector tokens = stim::parser::split(line, ' '); + + //create temporary storage for casting the token values + T val; + + //for each token (skipping the first) + for(unsigned int i = 1; i < tokens.size(); i++){ + std::stringstream ss(tokens[i]); //create a stringstream object for casting + ss>>val; //cast the token to the correct numerical value + push_back(val); //push the coordinate into the vertex + } + } + + //output the vertex as a string + std::string str(){ + + std::stringstream ss; + + for(int i = 0; i < size(); i++){ + if(i > 0) + ss<<' '; + + ss<{ + + //default constructor, empty triplet + triplet(){} + + //create a triplet given a parameter list (OBJ indices start at 1, so 0 can be used to indicate no value) + triplet(size_t v, size_t vt = 0, size_t vn = 0){ + push_back(v); + if(vn != 0){ + push_back(vt); + push_back(vn); + } + else if(vt != 0) + push_back(vt); + } + + //build a triplet from a string + triplet(std::string s){ + + //create a temporary vector used to store string tokens + std::vector tokens = stim::parser::split(s, '\\'); + + //std::cout<<"processing triplet: "<= 1) + push_back( atoi( tokens[0].c_str() ) ); + + if(tokens.size() >= 2){ + if(tokens[1].length()) + push_back( atoi( tokens[1].c_str() ) ); + else + push_back(0); + } + + if(tokens.size() == 3) + push_back( atoi( tokens[2].c_str() ) ); + + } + + //output the geometry as a string + std::string str(){ + + std::stringstream ss; + + ss<{ + + using std::vector::size; + using std::vector::at; + using std::vector::push_back; + using std::vector::resize; + + geometry(){} + + //constructs a geometry object from a line read from an OBJ file + geometry(std::string line){ + + //create a temporary vector used to store string tokens + std::vector tokens = stim::parser::split(line, ' '); + + resize(tokens.size() - 1); //set the geometry size based on the number of tokens + //for each triplet in the line + for(unsigned int i = 1; i < tokens.size(); i++){ + //construct the triplet and push it to the geometry + //push_back( triplet(tokens[i]) ); + at(i-1) = triplet(tokens[i]); + } + + } + + //returns a vector containing the vertex indices for the geometry + void get_v(std::vector& v){ + v.resize(size()); //resize the vector to match the number of vertices + for(unsigned int i = 0; i& vt){ + vt.resize(size()); //resize the vector to match the number of vertices + for(unsigned int i = 0; i& vn){ + vn.resize(size()); //resize the vector to match the number of vertices + for(unsigned int i = 0; i V; //vertex spatial position + std::vector VN; + std::vector VT; + + //structure lists + std::vector L; //list of lines + std::vector P; //list of points structures + std::vector F; //list of faces + + //material lists + std::vector< obj_material > M; //list of material descriptors + std::vector Mf; //face index where each material begins + + //information for the current geometric object + geometry current_geo; + vertex current_vt; + vertex current_vn; + obj_material current_material; //stores the current material + bool new_material; //flags if a material property has been changed since the last material was pushed + + //flags for the current geometric object + obj_type current_type; + bool geo_flag_vt; + bool geo_flag_vn; + bool vert_flag_vt; + bool vert_flag_vn; + + void update_vt(vertex vt){ + current_vt = vt; + + //the geometry vertex texture flag can only be set for the first vertex + if(current_geo.size() == 0) + geo_flag_vt = true; + + vert_flag_vt = true; + } + + void update_vn(vertex vn){ + current_vn = vn; + + //the geometry normal flag can only be set for the first vertex + if(current_geo.size() == 0) + geo_flag_vn = true; + + vert_flag_vn = true; + } + + //create a triple and add it to the current geometry + void update_v(vertex vv){ + + size_t v; + size_t vt = 0; + size_t vn = 0; + + //if the current geometry is using a texture coordinate, add the current texture coordinate to the geometry + if(geo_flag_vt){ + if(vert_flag_vt) //don't add it more than once + VT.push_back(current_vt); + vt = VT.size(); + } + + //if the current geometry is using a normal, add the current texture coordinate to the geometry + if(geo_flag_vn){ + if(vert_flag_vn) //don't add it more than once + VN.push_back(current_vn); + vn = VN.size(); + } + + //add the current vertex position to the geometry + V.push_back(vv); + v = V.size(); + + //create a triplet and add it to the current geometry + current_geo.push_back(triplet(v, vt, vn)); + + //clear the vertex flags + vert_flag_vt = false; + vert_flag_vn = false; + } + + void init(){ + //clear all lists + V.clear(); + VT.clear(); + VN.clear(); + P.clear(); + L.clear(); + F.clear(); + + //initialize all of the flags + current_type = OBJ_NONE; + geo_flag_vt = false; + geo_flag_vn = false; + vert_flag_vt = false; + vert_flag_vn = false; + + new_material = false; //initialize a new material to false (start with no material) + } + + //gets the type of token representing the entry in the OBJ file + token_type get_token(std::string s){ + + //if the line contains a vertex + if(s[0] == 'v'){ + if(s[1] == ' ') return OBJ_V; + if(s[1] == 't') return OBJ_VT; + if(s[1] == 'n') return OBJ_VN; + } + + if(s[0] == 'l' && s[1] == ' ') return OBJ_L; + if(s[0] == 'p' && s[1] == ' ') return OBJ_P; + if(s[0] == 'f' && s[1] == ' ') return OBJ_F; + + return OBJ_INVALID; + } + +public: + /// Constructor loads a Wavefront OBJ file + obj(std::string filename){ + load(filename); + } + + //functions for setting the texture coordinate for the next vertex + void TexCoord(T x){ update_vt(vertex(x));} + void TexCoord(T x, T y){ update_vt(vertex(x, y));} + void TexCoord(T x, T y, T z){ update_vt(vertex(x, y, z));} + void TexCoord(T x, T y, T z, T w){ update_vt(vertex(x, y, z, w));} + + //functions for setting the normal for the next vertex + void Normal(T x){ update_vn(vertex(x));} + void Normal(T x, T y){ update_vn(vertex(x, y));} + void Normal(T x, T y, T z){ update_vn(vertex(x, y, z));} + void Normal(T x, T y, T z, T w){ update_vn(vertex(x, y, z, w));} + + //functions for setting the next vertex position (note that this updates the current geometry) + void Vertex(T x){ update_v(vertex(x));} + void Vertex(T x, T y){ update_v(vertex(x, y));} + void Vertex(T x, T y, T z){ update_v(vertex(x, y, z));} + void Vertex(T x, T y, T z, T w){ update_v(vertex(x, y, z, w));} + + ///Material functions + void matKa(T r, T g, T b) { + new_material = true; + current_material.ka[0] = r; + current_material.ka[1] = g; + current_material.ka[2] = b; + } + void matKa(std::string tex = std::string()) { + new_material = true; + current_material.tex_ka = tex; + } + void matKd(T r, T g, T b) { + new_material = true; + current_material.kd[0] = r; + current_material.kd[1] = g; + current_material.kd[2] = b; + } + void matKd(std::string tex = std::string()) { + new_material = true; + current_material.tex_kd = tex; + } + void matKs(T r, T g, T b) { + new_material = true; + current_material.ks[0] = r; + current_material.ks[1] = g; + current_material.ks[2] = b; + } + void matKs(std::string tex = std::string()) { + new_material = true; + current_material.tex_ks = tex; + } + void matNs(T n) { + new_material = true; + current_material.ns = n; + } + void matNs(std::string tex = std::string()) { + new_material = true; + current_material.tex_ns = tex; + } + void matIllum(int i) { + new_material = true; + current_material.illum = i; + } + void matD(std::string tex = std::string()) { + new_material = true; + current_material.tex_alpha = tex; + } + void matBump(std::string tex = std::string()) { + new_material = true; + current_material.tex_bump = tex; + } + void matDisp(std::string tex = std::string()) { + new_material = true; + current_material.tex_disp = tex; + } + void matDecal(std::string tex = std::string()) { + new_material = true; + current_material.tex_decal = tex; + } + + ///This function starts drawing of a primitive object, such as a line, face, or point set + + /// @param t is the type of object to be drawn: OBJ_POINTS, OBJ_LINE, OBJ_FACE + void Begin(obj_type t){ + if (new_material) { //if a new material has been specified + if (current_material.name == "") { //if a name wasn't given, create a new one + std::stringstream ss; //create a name for it + ss << "material" << M.size(); //base it on the material number + current_material.name = ss.str(); + } + Mf.push_back(F.size()); //start the material at the current face index + M.push_back(current_material); //push the current material + current_material.name = ""; //reset the name of the current material + new_material = false; + } + current_type = t; + } + + //generates a list of faces from a list of points, assuming the input list forms a triangle strip + std::vector genTriangleStrip(geometry s) { + if (s.size() < 3) return std::vector(); //return an empty list if there aren't enough points to form a triangle + size_t nt = s.size() - 2; //calculate the number of triangles in the strip + std::vector r(nt); //create a list of geometry objects, where the number of faces = the number of triangles in the strip + + r[0].push_back(s[0]); + r[0].push_back(s[1]); + r[0].push_back(s[2]); + for (size_t i = 1; i < nt; i++) { + if (i % 2) { + r[i].push_back(s[i + 1]); + r[i].push_back(s[i + 0]); + r[i].push_back(s[i + 2]); + } + else { + r[i].push_back(s[i + 0]); + r[i].push_back(s[i + 1]); + r[i].push_back(s[i + 2]); + } + } + return r; + } + + /// This function terminates drawing of a primitive object, such as a line, face, or point set + void End(){ + //copy the current object to the appropriate list + if(current_geo.size() != 0) + { + switch(current_type){ + + case OBJ_NONE: + std::cout<<"STIM::OBJ error, objEnd() called before objBegin()."< tstrip = genTriangleStrip(current_geo); //generate a list of faces from the current geometry + F.insert(F.end(), tstrip.begin(), tstrip.end()); //insert all of the triangles into the face list + break; + } + } + //clear everything + current_type = OBJ_NONE; + current_geo.clear(); + vert_flag_vt = false; + vert_flag_vn = false; + geo_flag_vt = false; + geo_flag_vn = false; + } +//temporary convenience method + void rev(){ + if(current_geo.size() != 0) + // current_geo.reverse(current_geo.begin(), current_geo.end()); + std::reverse(current_geo.begin(), current_geo.end()); + } + + //output the OBJ structure as a string + std::string str(){ + + std::stringstream ss; + + unsigned int i; + + //output all of the vertices + if(V.size()){ + ss<<"#vertex positions"< getV(unsigned int vi){ + stim::vec v = V[vi]; + return v; + } + std::vector< stim::vec > getAllV() { + std::vector< stim::vec > v(V.size()); + for (unsigned i = 0; i < V.size(); i++) + v[i] = V[i]; + return v; + } + + std::vector< stim::vec > getAllV(std::vector vertexIndices){ + std::vector< stim::vec > allVerticesList; + for (unsigned int i=0; i >allVerticesList, stim::vec v0) + { + int result = 0; + for (unsigned int i = 0; i < allVerticesList.size() ; i++) + { + if (allVerticesList[i] == v0) + { + result = i; + } + } + return result; + } + + /// Retrieve the vertex texture coordinate at index i + /// @param vti is the desired index + stim::vec getVT(unsigned int vti){ + stim::vec vt = VT[vti]; + return vt; + } + + /// Retrieve the vertex normal at index i + /// @param vni is the desired index + stim::vec getVN(unsigned int vni){ + stim::vec vn = VN[vni]; + return vn; + } + + + /// Retrieve a vertex stored at a list of given indices + /// @param vi is an array containing a series of indices + std::vector< stim::vec > getV( std::vector vi ){ + + std::vector< stim::vec > v; + v.resize(vi.size()); //pre-allocate an array of vertices + + for(unsigned i = 0; i < vi.size(); i++) + v[i] = V[vi[i] - 1]; + + return v; //return the array of vertices + } + + /// Retrieve a vertex stored at a list of given indices + /// @param vi is an array containing a series of indices + std::vector< stim::vec > getVT( std::vector vti ){ + + std::vector< stim::vec > vt; + vt.resize(vti.size()); //pre-allocate an array of vertices + + for(unsigned i = 0; i < vti.size(); i++) + vt[i] = VT[vti[i] - 1]; + + return vt; //return the array of vertices + } + + /// Retrieve a vertex stored at a list of given indices + /// @param vi is an array containing a series of indices + std::vector< stim::vec > getVN( std::vector vni ){ + + std::vector< stim::vec > vn; + vn.resize(vni.size()); //pre-allocate an array of vertices + + for(unsigned i = 0; i < vni.size(); i++) + vn[i] = VN[vni[i] - 1]; + + return vn; //return the array of vertices + } + + stim::vec centroid(){ + + //get the number of coordinates + size_t N = V[0].size(); + + //allocate space for the minimum and maximum coordinate points (bounding box corners) + stim::vec vmin, vmax; + vmin.resize(N); + vmax.resize(N); + + //find the minimum and maximum value for each coordinate + size_t NV = V.size(); + for(size_t v = 0; v < NV; v++) + for(size_t i = 0; i < N; i++){ + + if(V[v][i] < vmin[i]) + vmin[i] = V[v][i]; + if(V[v][i] > vmax[i]) + vmax[i] = V[v][i]; + } + + //find the centroid using the min and max points + stim::vec c = vmin * 0.5 + vmax * 0.5; + + return c; + } + + stim::vec dimensions() { + //get the number of coordinates + size_t N = V[0].size(); + + //allocate space for the minimum and maximum coordinate points (bounding box corners) + stim::vec vmin, vmax; + vmin.resize(N); + vmax.resize(N); + + //find the minimum and maximum value for each coordinate + size_t NV = V.size(); + for (size_t v = 0; v < NV; v++) + for (size_t i = 0; i < N; i++) { + + if (V[v][i] < vmin[i]) + vmin[i] = V[v][i]; + if (V[v][i] > vmax[i]) + vmax[i] = V[v][i]; + } + + //find the soze using the min and max points + stim::vec d = vmax - vmin; + + return d; + } + + /// Returns the number of lines in the OBJ structure + unsigned int numL(){ + return (unsigned)L.size(); + } + + /// Returns all points in the line corresponding to a given index + + /// @param i is the index of the desired line + std::vector< stim::vec > getL_V(unsigned int i){ + + //get the number of points in the specified line + unsigned int nP = L[i].size(); + //create a vector of points + std::vector< stim::vec > l; + + //set the size of the vector + l.resize(nP); + + //copy the points from the point list to the stim vector + unsigned int pie; + for(unsigned int p = 0; p < nP; p++){ + + //get the index of the geometry point + pie = L[i][p][0] - 1; + + //get the coordinates of the current point + stim::vec newP = V[pie]; + + //copy the point into the vector + l[p] = newP; + } + + return l; + + } + + /// Returns the vertex indices for the specified line + /// @param i is the index of the line + std::vector< unsigned int > getL_Vi(unsigned int i){ + + unsigned int nP = L[i].size(); + + std::vector< unsigned int > l; + + //set the size of the vector + l.resize(nP); + + //copy the indices from the geometry structure to the array + for(unsigned int p = 0; p < nP; p++){ + + l[p] = L[i][p][0]; + + } + + return l; + } + + + /// Returns a vector containing the list of texture coordinates associated with each point of a line. + + /// @param i is the index of the desired line + std::vector< stim::vec > getL_VT(unsigned int i){ + + //get the number of points in the specified line + unsigned int nP = L[i].size(); + + //create a vector of points + std::vector< stim::vec > l; + + //set the size of the vector + l.resize(nP); + + //copy the points from the point list to the stim vector + unsigned int pie; + for(unsigned int p = 0; p < nP; p++){ + + //get the index of the geometry point + pie = L[i][p][1] - 1; + + //get the coordinates of the current point + stim::vec newP = VT[pie]; + + //copy the point into the vector + l[p] = newP; + } + + return l; + } + + /// Add an array of vertices to the vertex list + unsigned int addV(std::vector< stim::vec > vertices){ + + unsigned int NV = vertices.size(); //get the total number of new vertices + + unsigned int currentV = V.size() + 1; //store the index to the first submitted point + + //for each vertex + for(unsigned int vi = 0; vi < NV; vi++){ + vertex v(vertices[vi]); + V.push_back(v); + } + + //return the index to the first point + return currentV; + + } + + /// Add a line to the object + void addLine(std::vector v, std::vector vt = std::vector(), std::vector vn = std::vector()){ + + //create a new line geometry + geometry new_line; + + unsigned int v_i, vt_i, vn_i; + for(unsigned int i = 0; i < v.size(); i++){ + v_i = vt_i = vn_i = 0; + + v_i = v[i]; + if(vt.size() != 0) + vt_i = vt[i]; + if(vn.size() != 0) + vn_i = vn[i]; + + new_line.push_back(triplet(v_i, vt_i, vn_i)); + } + + //push the new geometry to the line list + L.push_back(new_line); + } + + /// Fills three std::vector structures with the indices representing a line + /// @param l is the line index + /// @param vi is a vertex index array that will be filled + void getLinei(unsigned l, std::vector& vi){ + L[l-1].get_v(vi); + } + + /// Fills three std::vector structures with the indices representing a line + /// @param l is the line index + /// @param vi is a vertex index array that will be filled + /// @param vti is a vertex texture coordinate index array that will be filled + void getLinei(unsigned l, std::vector& vi, std::vector& vti){ + getLinei(l, vi); + L[l-1].get_vt(vti); + } + + /// Fills three std::vector structures with the indices representing a line + /// @param l is the line index + /// @param vi is a vertex index array that will be filled + /// @param vti is a vertex texture coordinate index array that will be filled + /// @param vni is a vertex normal index array that will be filled + void getLinei(unsigned l, std::vector& vi, std::vector& vti, std::vector& vni){ + getLinei(l, vi, vti); + L[l-1].get_vn(vni); + } + + /// Returns a list of points corresponding to the vertices of a line + void getLine(unsigned l, std::vector< stim::vec >& v){ + + std::vector vi; //create a vector to store indices to vertices + getLinei(l, vi); //get the indices for the line vertices + v = getV(vi); //get the vertices corresponding to the indices + } + + void getLine(unsigned l, std::vector< stim::vec >& v, std::vector< stim::vec >& vt){ + + std::vector vi, vti; + getLinei(l, vi, vti); //get the indices for the line vertices + v = getV(vi); //get the vertices corresponding to the indices + vt = getVT(vti); + } + + void getLine(unsigned l, std::vector< stim::vec >& v, + std::vector< stim::vec >& vt, + std::vector< stim::vec >& vn){ + + std::vector vi, vti, vni; + getLinei(l, vi, vti, vni); //get the indices for the line vertices + v = getV(vi); //get the vertices corresponding to the indices + vt = getVT(vti); + vn = getVN(vni); + + } + + /// Return the number of vertices in a line + /// @param l is the line index + unsigned getLineN(unsigned l) { + return (unsigned)L[l].size(); + } + + + + +}; + + + + + + +}; + + +#endif diff --git a/tira/visualization/obj/obj_material.h b/tira/visualization/obj/obj_material.h new file mode 100644 index 0000000..688f28a --- /dev/null +++ b/tira/visualization/obj/obj_material.h @@ -0,0 +1,58 @@ +#ifndef OBJ_MATERIAL_H +#define OBJ_MATERIAL_H + +#include +#include + +namespace stim { + +template +struct obj_material { + std::string name; //material name + T ka[3]; //ambient color + T kd[3]; //diffuse color + T ks[3]; //specular color + T ns; //specular exponent + + int illum; //illumination model + + std::string tex_ka; //ambient texture + std::string tex_kd; //diffuse texture + std::string tex_ks; //specular texture + std::string tex_ns; //texture map for the specular exponent + std::string tex_alpha; //texture map for the alpha component + std::string tex_bump; //bump map + std::string tex_disp; //displacement map + std::string tex_decal; //stencil decal + + obj_material() { //constructor + std::memset(ka, 0, sizeof(T) * 3); + std::memset(kd, 0, sizeof(T) * 3); + std::memset(ks, 0, sizeof(T) * 3); + ns = 10; + illum = 2; + + } + std::string str() { + std::stringstream ss; + ss << "newmtl " << name << std::endl; + ss << "Ka " << ka[0] << " " << ka[1] << " " << ka[2] << std::endl; + ss << "Kd " << kd[0] << " " << kd[1] << " " << kd[2] << std::endl; + ss << "Ks " << ks[0] << " " << ks[1] << " " << ks[2] << std::endl; + ss << "Ns " << ns << std::endl; + ss << "illum " << illum << std::endl; + if (tex_ka != "") ss << "map_Ka " << tex_ka << std::endl; + if (tex_kd != "") ss << "map_Kd " << tex_kd << std::endl; + if (tex_ks != "") ss << "map_Ks " << tex_ks << std::endl; + if (tex_ns != "") ss << "map_Ns " << tex_ns << std::endl; + if (tex_alpha != "") ss << "map_d " << tex_alpha << std::endl; + if (tex_bump != "") ss << "bump " << tex_bump << std::endl; + if (tex_disp != "") ss << "disp " << tex_disp << std::endl; + if (tex_decal != "") ss << "decal " << tex_decal << std::endl; + return ss.str(); + } +}; + +} //end namespace stim + +#endif \ No newline at end of file diff --git a/tira/visualization/swc.h b/tira/visualization/swc.h new file mode 100644 index 0000000..076d5b8 --- /dev/null +++ b/tira/visualization/swc.h @@ -0,0 +1,284 @@ +#ifndef STIM_SWC_H +#define STIM_SWC_H + +#include +#include +#include +#include + +//STIM includes +#include +#include + +namespace stim { + namespace swc_tree { + template + class swc_node { + + protected: + enum neuronal_type { SWC_UNDEFINED, SWC_SOMA, SWC_AXON, SWC_DENDRITE, SWC_APICAL_DENDRITE, SWC_FORK_POINT, SWC_END_POINT, SWC_CUSTOM }; // eight types + enum node_information { INTEGER_LABEL, NEURONAL_TYPE, X_COORDINATE, Y_COORDINATE, Z_COORDINATE, RADIUS, PARENT_LABEL }; + + public: + int idx; // the index of current node start from 1(original index from the file) + neuronal_type type; // the type of neuronal segmemt + stim::vec3 point; // the point coordinates + T radius; // the radius at current node + int parent_idx; // parent idx of current node, -1 when it is origin(soma) + int level; // tree level + std::vector son_idx; // son idx of current node + + swc_node() { // default constructor + idx = -1; // set to default -1 + parent_idx = -1; // set to origin -1 + level = -1; // set to default -1 + type = SWC_UNDEFINED; // set to undefined type + radius = 0; // set to 0 + } + + void get_node(std::string line) { // get information from the node point we got + + // create a vector to store the information of one node point + std::vector p = stim::parser::split(line, ' '); + + // for each information contained in the node point we got + for (unsigned int i = 0; i < p.size(); i++) { + std::stringstream ss(p[i]); // create a stringstream object for casting + + // store different information + switch (i) { + case INTEGER_LABEL: + ss >> idx; // cast the stringstream to the correct numerical value + break; + case NEURONAL_TYPE: + int tmp_type; + ss >> tmp_type; // cast the stringstream to the correct numerical value + type = (neuronal_type)tmp_type; + break; + case X_COORDINATE: + T tmp_X; + ss >> tmp_X; // cast the stringstream to the correct numerical value + point[0] = tmp_X; // store the X_coordinate in vec3[0] + break; + case Y_COORDINATE: + T tmp_Y; + ss >> tmp_Y; // cast the stringstream to the correct numerical value + point[1] = tmp_Y; // store the Y_coordinate in vec3[1] + break; + case Z_COORDINATE: + T tmp_Z; + ss >> tmp_Z; // cast the stringstream to the correct numerical value + point[2] = tmp_Z; // store the Z_coordinate in vec3[2] + break; + case RADIUS: + ss >> radius; // cast the stringstream to the correct numerical value + break; + case PARENT_LABEL: + ss >> parent_idx; // cast the stringstream to the correct numerical value + break; + } + } + } + }; + } // end of namespace swc_tree + + template + class swc { + public: + std::vector< typename swc_tree::swc_node > node; + std::vector< std::vector > E; // list of nodes + + swc() {}; // default constructor + + // load the swc as tree nodes + void load(std::string filename) { + + // load the file + std::ifstream infile(filename.c_str()); + + // if the file is invalid, throw an error + if (!infile) { + std::cerr << "STIM::SWC Error loading file" << filename << std::endl; + exit(-1); + } + + std::string line; + // skip comment + while (getline(infile, line)) { + if ('#' == line[0] || line.empty()) // if it is comment line or empty line + continue; // keep read + else + break; + } + + unsigned int l = 0; // number of nodes + + // get rid of the first/origin node + swc_tree::swc_node new_node; + new_node.get_node(line); + l++; + node.push_back(new_node); // push back the first node + + getline(infile, line); // get a new line + // keep reading the following node point information as string + while (!line.empty()) { // test for the last empty line + l++; // still remaining node to be read + + swc_tree::swc_node next_node; + next_node.get_node(line); + node.push_back(next_node); + + getline(infile, line); // get a new line + } + } + + // read the head comment from swc file + void read_comment(std::string filename) { + + // load the file + std::ifstream infile(filename.c_str()); + + // if the file is invalid, throw an error + if (!infile) { + std::cerr << "STIM::SWC Error loading file" << filename << std::endl; + exit(1); + } + + std::string line; + while (getline(infile, line)) { + if ('#' == line[0] || line.empty()) { + std::cout << line << std::endl; // print the comment line by line + } + else + break; // break when reaches to node information + } + } + + // link those nodes to create a tree + void create_tree() { + unsigned n = node.size(); // get the total number of node point + int cur_level = 0; + + // build the origin(soma) + node[0].level = cur_level; + + // go through follow nodes + for (unsigned i = 1; i < n; i++) { + if (node[i].parent_idx != node[i - 1].parent_idx) + cur_level = node[node[i].parent_idx - 1].level + 1; + node[i].level = cur_level; + int tmp_parent_idx = node[i].parent_idx - 1; // get the parent node loop idx of current node + node[tmp_parent_idx].son_idx.push_back(i + 1); // son_idx stores the real idx = loop idx + 1 + } + } + + // create a new edge + void create_up(std::vector& edge,swc_tree::swc_node cur_node, int target) { + while (cur_node.parent_idx != target) { + edge.push_back(cur_node.idx); + cur_node = node[cur_node.parent_idx - 1]; // move to next node + } + edge.push_back(cur_node.idx); // push back the start/end vertex of current edge + + std::reverse(edge.begin(), edge.end()); // follow the original flow direction + } + void create_down(std::vector& edge, swc_tree::swc_node cur_node, int j) { + while (cur_node.son_idx.size() != 0) { + edge.push_back(cur_node.idx); + if (cur_node.son_idx.size() > 1) + cur_node = node[cur_node.son_idx[j] - 1]; // move to next node + else + cur_node = node[cur_node.son_idx[0] - 1]; + } + edge.push_back(cur_node.idx); // push back the start/end vertex of current edge + } + + // resample the tree-like SWC + void resample() { + unsigned nn = node.size(); + + std::vector joint_node; + for (unsigned i = 1; i < nn; i++) { // search all nodes(except the first one) to find joint nodes + if (node[i].son_idx.size() > 1) { + joint_node.push_back(node[i].idx); // store the original index + } + } + + std::vector new_edge; // new edge in the network + + unsigned n = joint_node.size(); + + for (unsigned i = 0; i < n; i++) { // for every joint nodes + + swc_tree::swc_node cur_node = node[joint_node[i] - 1]; // store current node + + // go up + swc_tree::swc_node tmp_node = node[cur_node.parent_idx - 1]; + while (tmp_node.parent_idx != -1 && tmp_node.son_idx.size() == 1) { + tmp_node = node[tmp_node.parent_idx - 1]; + } + int target = tmp_node.parent_idx; // store the go-up target + create_up(new_edge, cur_node, target); + E.push_back(new_edge); + new_edge.clear(); + + // go down + unsigned t = cur_node.son_idx.size(); + for (unsigned j = 0; j < t; j++) { // for every son node + tmp_node = node[cur_node.son_idx[j] - 1]; // move down + while (tmp_node.son_idx.size() == 1) { + tmp_node = node[tmp_node.son_idx[0] - 1]; + } + if (tmp_node.son_idx.size() == 0) { + create_down(new_edge, cur_node, j); + E.push_back(new_edge); + new_edge.clear(); + } + } + } + + if (n == 0) { // just one edge + new_edge.clear(); + for (unsigned i = 0; i < nn; i++) + new_edge.push_back(node[i].idx); + E.push_back(new_edge); + } + } + + // get points in one edge + void get_points(int e, std::vector< stim::vec3 >& V) { + V.resize(E[e].size()); + for (unsigned i = 0; i < E[e].size(); i++) { + unsigned id = E[e][i] - 1; + + V[i][0] = node[id].point[0]; + V[i][1] = node[id].point[1]; + V[i][2] = node[id].point[2]; + } + } + + // get radius information in one edge + void get_radius(int e, std::vector& radius) { + radius.resize(E[e].size()); + for (unsigned i = 0; i < E[e].size(); i++) { + unsigned id = E[e][i] - 1; + + radius[i] = node[id].radius; + } + } + + // return the number of point in swc + unsigned int numP() { + return node.size(); + } + + // return the number of edges in swc after resample + unsigned int numE() { + return E.size(); + } + + + }; +} // end of namespace stim + +#endif \ No newline at end of file -- libgit2 0.21.4