// Filename: pfmFile.cxx // Created by: drose (23Dec10) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) Carnegie Mellon University. All rights reserved. // // All use of this software is subject to the terms of the revised BSD // license. You should have received a copy of this license along // with this source code in a file named "LICENSE." // //////////////////////////////////////////////////////////////////// #include "config_pnmimage.h" #include "pfmFile.h" #include "virtualFileSystem.h" #include "pandaFileStream.h" #include "littleEndian.h" #include "bigEndian.h" #include "cmath.h" #include "pnmImage.h" #include "pnmReader.h" #include "pnmWriter.h" #include "string_utils.h" #include "look_at.h" //////////////////////////////////////////////////////////////////// // Function: PfmFile::Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// PfmFile:: PfmFile() { _has_no_data_value = false; _no_data_value = LPoint4f::zero(); _has_point = has_point_noop; clear(); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::Copy Constructor // Access: Published // Description: //////////////////////////////////////////////////////////////////// PfmFile:: PfmFile(const PfmFile ©) : PNMImageHeader(copy), _table(copy._table), _scale(copy._scale), _has_no_data_value(copy._has_no_data_value), _no_data_value(copy._no_data_value), _has_point(copy._has_point) { } //////////////////////////////////////////////////////////////////// // Function: PfmFile::Copy Assignment // Access: Published // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: operator = (const PfmFile ©) { PNMImageHeader::operator = (copy); _table = copy._table; _scale = copy._scale; _has_no_data_value = copy._has_no_data_value; _no_data_value = copy._no_data_value; _has_point = copy._has_point; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::clear // Access: Published // Description: Eliminates all data in the file. //////////////////////////////////////////////////////////////////// void PfmFile:: clear() { _x_size = 0; _y_size = 0; _scale = 1.0; _num_channels = 0; _table.clear(); clear_no_data_value(); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::clear // Access: Published // Description: Resets to an empty table with a specific size. //////////////////////////////////////////////////////////////////// void PfmFile:: clear(int x_size, int y_size, int num_channels) { nassertv(x_size >= 0 && y_size >= 0); _x_size = x_size; _y_size = y_size; _scale = 1.0; _num_channels = num_channels; _table.clear(); int size = _x_size * _y_size * _num_channels; // We allocate a little bit bigger to allow safe overflow: you can // call get_point3() or get_point4() on the last point of a 1- or // 3-channel image. _table.insert(_table.end(), size + 4, (PN_float32)0.0); clear_no_data_value(); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::read // Access: Published // Description: Reads the PFM data from the indicated file, returning // true on success, false on failure. // // This can also handle reading a standard image file // supported by PNMImage; it will be quietly converted // to a floating-point type. //////////////////////////////////////////////////////////////////// bool PfmFile:: read(const Filename &fullpath) { VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); Filename filename = Filename::binary_filename(fullpath); PT(VirtualFile) file = vfs->get_file(filename); if (file == (VirtualFile *)NULL) { // No such file. pnmimage_cat.error() << "Could not find " << fullpath << "\n"; return false; } if (pnmimage_cat.is_debug()) { pnmimage_cat.debug() << "Reading PFM file " << filename << "\n"; } istream *in = file->open_read_file(true); bool success = read(*in, fullpath); vfs->close_read_file(in); return success; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::read // Access: Published // Description: Reads the PFM data from the indicated stream, // returning true on success, false on failure. // // This can also handle reading a standard image file // supported by PNMImage; it will be quietly converted // to a floating-point type. //////////////////////////////////////////////////////////////////// bool PfmFile:: read(istream &in, const Filename &fullpath) { PNMReader *reader = make_reader(&in, false, fullpath); if (reader == (PNMReader *)NULL) { clear(); return false; } return read(reader); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::read // Access: Published // Description: Reads the PFM data using the indicated PNMReader. // // The PNMReader is always deleted upon completion, // whether successful or not. //////////////////////////////////////////////////////////////////// bool PfmFile:: read(PNMReader *reader) { clear(); if (reader == NULL) { return false; } if (!reader->is_valid()) { delete reader; return false; } if (!reader->is_floating_point()) { // Not a floating-point file. Quietly convert it. PNMImage pnm; if (!pnm.read(reader)) { return false; } return load(pnm); } bool success = reader->read_pfm(*this); delete reader; return success; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::write // Access: Published // Description: Writes the PFM data to the indicated file, returning // true on success, false on failure. // // This can also handle writing a standard image file // supported by PNMImage, if the filename extension is // some image type's extension other than "pfm"; it // will be quietly converted to the appropriate integer // type. //////////////////////////////////////////////////////////////////// bool PfmFile:: write(const Filename &fullpath) { if (!is_valid()) { pnmimage_cat.error() << "PFM file is invalid.\n"; return false; } Filename filename = Filename::binary_filename(fullpath); pofstream out; if (!filename.open_write(out)) { pnmimage_cat.error() << "Unable to open " << filename << "\n"; return false; } string extension = downcase(fullpath.get_extension()); if (extension != "pfm") { // Maybe we're trying to write a different kind of image file. PNMImage pnm; PNMWriter *writer = pnm.make_writer(&out, false, fullpath, NULL); if (writer != (PNMWriter *)NULL) { // Yep. if (store(pnm)) { return pnm.write(writer); } // Couldn't make an image. Carry on directly. delete writer; } } if (pnmimage_cat.is_debug()) { pnmimage_cat.debug() << "Writing PFM file " << filename << "\n"; } return write(out, fullpath); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::write // Access: Published // Description: Writes the PFM data to the indicated stream, // returning true on success, false on failure. //////////////////////////////////////////////////////////////////// bool PfmFile:: write(ostream &out, const Filename &fullpath) { if (!is_valid()) { return false; } PNMWriter *writer = make_writer(fullpath); if (writer == (PNMWriter *)NULL) { return false; } return write(writer); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::write // Access: Published // Description: Writes the PFM data using the indicated PNMWriter. // // The PNMWriter is always deleted upon completion, // whether successful or not. //////////////////////////////////////////////////////////////////// bool PfmFile:: write(PNMWriter *writer) { if (writer == NULL) { return false; } if (!is_valid()) { delete writer; return false; } writer->copy_header_from(*this); if (!writer->supports_floating_point()) { // Hmm, it's an integer file type. Convert it from the // floating-point data we have. PNMImage pnmimage; if (!store(pnmimage)) { delete writer; return false; } writer->copy_header_from(pnmimage); bool success = (writer->write_data(pnmimage.get_array(), pnmimage.get_alpha_array()) != 0); delete writer; return success; } bool success = writer->write_pfm(*this); delete writer; return success; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::load // Access: Published // Description: Fills the PfmFile with the data from the indicated // PNMImage, converted to floating-point values. //////////////////////////////////////////////////////////////////// bool PfmFile:: load(const PNMImage &pnmimage) { if (!pnmimage.is_valid()) { clear(); return false; } // Get the number of channels, ignoring alpha. int num_channels; switch (pnmimage.get_num_channels()) { case 4: num_channels = 4; break; case 3: num_channels = 3; break; case 2: case 1: num_channels = 1; break; default: num_channels = 3; } clear(pnmimage.get_x_size(), pnmimage.get_y_size(), num_channels); switch (num_channels) { case 1: { for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) { for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) { _table[yi * _x_size + xi] = pnmimage.get_gray(xi, yi); } } } break; case 3: { for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) { for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) { PN_float32 *point = &_table[(yi * _x_size + xi) * _num_channels]; LRGBColord xel = pnmimage.get_xel(xi, yi); point[0] = xel[0]; point[1] = xel[1]; point[2] = xel[2]; } } } break; case 4: { for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) { for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) { PN_float32 *point = &_table[(yi * _x_size + xi) * _num_channels]; LRGBColord xel = pnmimage.get_xel(xi, yi); point[0] = xel[0]; point[1] = xel[1]; point[2] = xel[2]; point[3] = pnmimage.get_alpha(xi, yi); } } } break; default: nassertr(false, false); } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::store // Access: Published // Description: Copies the data to the indicated PNMImage, converting // to RGB values. //////////////////////////////////////////////////////////////////// bool PfmFile:: store(PNMImage &pnmimage) const { if (!is_valid()) { pnmimage.clear(); return false; } int num_channels = get_num_channels(); pnmimage.clear(get_x_size(), get_y_size(), num_channels, PGM_MAXMAXVAL); switch (num_channels) { case 1: { for (int yi = 0; yi < get_y_size(); ++yi) { for (int xi = 0; xi < get_x_size(); ++xi) { pnmimage.set_gray(xi, yi, _table[yi * _x_size + xi]); } } } break; case 3: { for (int yi = 0; yi < get_y_size(); ++yi) { for (int xi = 0; xi < get_x_size(); ++xi) { const LPoint3f &point = get_point(xi, yi); pnmimage.set_xel(xi, yi, point[0], point[1], point[2]); } } } break; case 4: { for (int yi = 0; yi < get_y_size(); ++yi) { for (int xi = 0; xi < get_x_size(); ++xi) { const LPoint4f &point = get_point4(xi, yi); pnmimage.set_xel(xi, yi, point[0], point[1], point[2]); pnmimage.set_alpha(xi, yi, point[3]); } } } break; default: nassertr(false, false); } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::store_mask // Access: Published // Description: Stores 1 or 0 values into the indicated PNMImage, // according to has_point() for each pixel. Each valid // point gets a 1 value; each nonexistent point gets a 0 // value. //////////////////////////////////////////////////////////////////// bool PfmFile:: store_mask(PNMImage &pnmimage) const { if (!is_valid()) { pnmimage.clear(); return false; } pnmimage.clear(get_x_size(), get_y_size(), 1, 255); for (int yi = 0; yi < get_y_size(); ++yi) { for (int xi = 0; xi < get_x_size(); ++xi) { pnmimage.set_gray(xi, yi, has_point(xi, yi)); } } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::fill // Access: Published // Description: Fills the table with all of the same value. //////////////////////////////////////////////////////////////////// void PfmFile:: fill(const LPoint4f &value) { switch (_num_channels) { case 1: { for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { _table[(yi * _x_size + xi)] = value[0]; } } } break; case 2: { for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { (*(LPoint2f *)&_table[(yi * _x_size + xi) * _num_channels]).set(value[0], value[1]); } } } break; case 3: { for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { (*(LPoint3f *)&_table[(yi * _x_size + xi) * _num_channels]).set(value[0], value[1], value[2]); } } } break; case 4: { for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { *(LPoint4f *)&_table[(yi * _x_size + xi) * _num_channels] = value; } } } break; } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_average_point // Access: Published // Description: Computes the unweighted average point of all points // within the box centered at (x, y) with the indicated // Manhattan-distance radius. Missing points are // assigned the value of their nearest neighbor. // Returns true if successful, or false if the point // value cannot be determined. //////////////////////////////////////////////////////////////////// bool PfmFile:: calc_average_point(LPoint3f &result, PN_float32 x, PN_float32 y, PN_float32 radius) const { result = LPoint3f::zero(); int min_x = int(ceil(x - radius)); int min_y = int(ceil(y - radius)); int max_x = int(floor(x + radius)); int max_y = int(floor(y + radius)); // We first construct a mini-grid of x_size by y_size integer values // to index into the main table. This indirection allows us to fill // in the holes in the mini-grid with the nearest known values // before we compute the average. int x_size = max_x - min_x + 1; int y_size = max_y - min_y + 1; int size = x_size * y_size; if (size == 0) { return false; } pvector mini_grid; mini_grid.insert(mini_grid.end(), size, MiniGridCell()); // Now collect the known data points and apply them to the // mini-grid. min_x = max(min_x, 0); min_y = max(min_y, 0); max_x = min(max_x, _x_size - 1); max_y = min(max_y, _y_size - 1); bool got_any = false; int xi, yi; for (yi = min_y; yi <= max_y; ++yi) { for (xi = min_x; xi <= max_x; ++xi) { if (!has_point(xi, yi)) { continue; } const LPoint3f &p = get_point(xi, yi); int gi = (yi - min_y) * y_size + (xi - min_x); nassertr(gi >= 0 && gi < size, false); mini_grid[gi]._sxi = xi; mini_grid[gi]._syi = yi; mini_grid[gi]._dist = 0; got_any = true; } } if (!got_any) { return false; } // Now recursively fill in any missing holes in the mini-grid. for (yi = 0; yi < y_size; ++yi) { for (xi = 0; xi < x_size; ++xi) { int gi = yi * x_size + xi; if (mini_grid[gi]._dist == 0) { int sxi = mini_grid[gi]._sxi; int syi = mini_grid[gi]._syi; fill_mini_grid(&mini_grid[0], x_size, y_size, xi + 1, yi, 1, sxi, syi); fill_mini_grid(&mini_grid[0], x_size, y_size, xi - 1, yi, 1, sxi, syi); fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi + 1, 1, sxi, syi); fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi - 1, 1, sxi, syi); } } } // Now the mini-grid is completely filled, so we can compute the // average. for (int gi = 0; gi < size; ++gi) { int sxi = mini_grid[gi]._sxi; int syi = mini_grid[gi]._syi; const LPoint3f &p = get_point(sxi, syi); result += p; } result /= PN_float32(size); return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_bilinear_point // Access: Published // Description: Computes the weighted average of the four nearest // points to the floating-point index (x, y). Returns // true if the point has any contributors, false if the // point is unknown. //////////////////////////////////////////////////////////////////// bool PfmFile:: calc_bilinear_point(LPoint3f &result, PN_float32 x, PN_float32 y) const { result = LPoint3f::zero(); x *= _x_size; y *= _y_size; int min_x = int(floor(x)); int min_y = int(floor(y)); PN_float32 frac_x = x - min_x; PN_float32 frac_y = y - min_y; LPoint3f p00, p01, p10, p11; PN_float32 w00 = 0.0, w01 = 0.0, w10 = 0.0, w11 = 0.0; if (has_point(min_x, min_y)) { w00 = (1.0 - frac_y) * (1.0 - frac_x); p00 = get_point(min_x, min_y); } if (has_point(min_x + 1, min_y)) { w10 = (1.0 - frac_y) * frac_x; p10 = get_point(min_x + 1, min_y); } if (has_point(min_x, min_y + 1)) { w01 = frac_y * (1.0 - frac_x); p01 = get_point(min_x, min_y + 1); } if (has_point(min_x + 1, min_y + 1)) { w11 = frac_y * frac_x; p11 = get_point(min_x + 1, min_y + 1); } PN_float32 net_w = w00 + w01 + w10 + w11; if (net_w == 0.0) { return false; } result = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11) / net_w; return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_min_max // Access: Published // Description: Calculates the minimum and maximum x, y, and z depth // component values, representing the bounding box of // depth values, and places them in the indicated // vectors. Returns true if successful, false if the // mesh contains no points. //////////////////////////////////////////////////////////////////// bool PfmFile:: calc_min_max(LVecBase3f &min_depth, LVecBase3f &max_depth) const { bool any_points = false; min_depth = LVecBase3f::zero(); max_depth = LVecBase3f::zero(); for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { if (!has_point(xi, yi)) { continue; } const LPoint3f &p = get_point(xi, yi); if (!any_points) { min_depth = p; max_depth = p; any_points = true; } else { min_depth[0] = min(min_depth[0], p[0]); min_depth[1] = min(min_depth[1], p[1]); min_depth[2] = min(min_depth[2], p[2]); max_depth[0] = max(max_depth[0], p[0]); max_depth[1] = max(max_depth[1], p[1]); max_depth[2] = max(max_depth[2], p[2]); } } } return any_points; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_autocrop // Access: Published // Description: Computes the minimum range of x and y across the PFM // file that include all points. If there are no points // with no_data_value in the grid--that is, all points // are included--then this will return (0, get_x_size(), // 0, get_y_size()). //////////////////////////////////////////////////////////////////// bool PfmFile:: calc_autocrop(int &x_begin, int &x_end, int &y_begin, int &y_end) const { y_begin = 0; while (is_row_empty(y_begin, 0, _x_size)) { ++y_begin; if (y_begin >= _y_size) { // We've reached the end; the entire grid is empty. x_begin = x_end = y_begin = y_end = 0; return false; } } y_end = _y_size; while (is_row_empty(y_end - 1, 0, _x_size)) { --y_end; nassertr(y_end > y_begin, false); } // Now we've got the top and bottom bounds. x_begin = 0; while (is_column_empty(x_begin, y_begin, y_end)) { ++x_begin; nassertr(x_begin < _x_size, false); } x_end = _x_size; while (is_column_empty(x_end - 1, y_begin, y_end)) { --x_end; nassertr(x_end > x_begin, false); } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::is_row_empty // Access: Published // Description: Returns true if all of the points on row y, in the range // [x_begin, x_end), are the no_data value, or false if // any one of these points has a value. //////////////////////////////////////////////////////////////////// bool PfmFile:: is_row_empty(int y, int x_begin, int x_end) const { nassertr(y >= 0 && y < _y_size && x_begin >= 0 && x_begin <= x_end && x_end <= _x_size, false); if (!_has_no_data_value) { return false; } for (int x = x_begin; x < x_end; ++x) { if (has_point(x, y)) { return false; } } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::is_column_empty // Access: Published // Description: Returns true if all of the points on column x, from // [y_begin, y_end), are the no_data value, or false if // any one of these points has a value. //////////////////////////////////////////////////////////////////// bool PfmFile:: is_column_empty(int x, int y_begin, int y_end) const { nassertr(x >= 0 && x < _x_size && y_begin >= 0 && y_begin <= y_end && y_end <= _y_size, false); if (!_has_no_data_value) { return false; } for (int y = y_begin; y < y_end; ++y) { if (has_point(x, y)) { return false; } } return true; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::set_no_data_value // Access: Published // Description: Sets the special value that means "no data" when it // appears in the pfm file. //////////////////////////////////////////////////////////////////// void PfmFile:: set_no_data_value(const LPoint4f &no_data_value) { _has_no_data_value = true; _no_data_value = no_data_value; switch (_num_channels) { case 1: _has_point = has_point_1; break; case 3: _has_point = has_point_3; break; case 4: _has_point = has_point_4; break; default: nassertv(false); } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::resize // Access: Published // Description: Applies a simple filter to resample the pfm file // in-place to the indicated size. Don't confuse this // with applying a scale to all of the points via // xform(). //////////////////////////////////////////////////////////////////// void PfmFile:: resize(int new_x_size, int new_y_size) { if (_x_size == 0 || _y_size == 0 || new_x_size == 0 || new_y_size == 0) { clear(new_x_size, new_y_size, _num_channels); return; } if (new_x_size == _x_size && new_y_size == _y_size) { return; } PfmFile result; result.clear(new_x_size, new_y_size, _num_channels); if (new_x_size < _x_size && new_y_size < _y_size) { // If we're downscaling, we can use quick_filter, which is faster. result.quick_filter_from(*this); } else { // Otherwise, we should use box_filter(), which is more general. result.box_filter_from(0.5, *this); } _table.swap(result._table); _x_size = new_x_size; _y_size = new_y_size; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::quick_filter_from // Access: Public // Description: Resizes from the given image, with a fixed radius of // 0.5. This is a very specialized and simple algorithm // that doesn't handle dropping below the Nyquist rate // very well, but is quite a bit faster than the more // general box_filter(), above. //////////////////////////////////////////////////////////////////// void PfmFile:: quick_filter_from(const PfmFile &from) { if (_x_size == 0 || _y_size == 0) { return; } Table new_data; new_data.reserve(_table.size()); PN_float32 from_x0, from_x1, from_y0, from_y1; int orig_x_size = from.get_x_size(); int orig_y_size = from.get_y_size(); PN_float32 x_scale = 1.0; PN_float32 y_scale = 1.0; if (_x_size > 1) { x_scale = (PN_float32)orig_x_size / (PN_float32)_x_size; } if (_y_size > 1) { y_scale = (PN_float32)orig_y_size / (PN_float32)_y_size; } switch (_num_channels) { case 1: { from_y0 = 0.0; for (int to_y = 0; to_y < _y_size; ++to_y) { from_y1 = (to_y + 1.0) * y_scale; from_y1 = min(from_y1, (PN_float32)orig_y_size); from_x0 = 0.0; for (int to_x = 0; to_x < _x_size; ++to_x) { from_x1 = (to_x + 1.0) * x_scale; from_x1 = min(from_x1, (PN_float32)orig_x_size); // Now the box from (from_x0, from_y0) - (from_x1, from_y1) // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y). PN_float32 result; from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1); new_data.push_back(result); from_x0 = from_x1; } from_y0 = from_y1; } } break; case 3: { from_y0 = 0.0; for (int to_y = 0; to_y < _y_size; ++to_y) { from_y1 = (to_y + 1.0) * y_scale; from_y1 = min(from_y1, (PN_float32)orig_y_size); from_x0 = 0.0; for (int to_x = 0; to_x < _x_size; ++to_x) { from_x1 = (to_x + 1.0) * x_scale; from_x1 = min(from_x1, (PN_float32)orig_x_size); // Now the box from (from_x0, from_y0) - (from_x1, from_y1) // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y). LPoint3f result; from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1); new_data.push_back(result[0]); new_data.push_back(result[1]); new_data.push_back(result[2]); from_x0 = from_x1; } from_y0 = from_y1; } } break; case 4: { from_y0 = 0.0; for (int to_y = 0; to_y < _y_size; ++to_y) { from_y1 = (to_y + 1.0) * y_scale; from_y1 = min(from_y1, (PN_float32)orig_y_size); from_x0 = 0.0; for (int to_x = 0; to_x < _x_size; ++to_x) { from_x1 = (to_x + 1.0) * x_scale; from_x1 = min(from_x1, (PN_float32)orig_x_size); // Now the box from (from_x0, from_y0) - (from_x1, from_y1) // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y). LPoint4f result; from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1); new_data.push_back(result[0]); new_data.push_back(result[1]); new_data.push_back(result[2]); new_data.push_back(result[3]); from_x0 = from_x1; } from_y0 = from_y1; } } break; default: nassertv(false); } new_data.push_back(0.0); new_data.push_back(0.0); new_data.push_back(0.0); new_data.push_back(0.0); nassertv(new_data.size() == _table.size()); _table.swap(new_data); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::reverse_rows // Access: Published // Description: Performs an in-place reversal of the row (y) data. //////////////////////////////////////////////////////////////////// void PfmFile:: reverse_rows() { nassertv(is_valid()); Table reversed; reversed.reserve(_table.size()); int row_size = _x_size * _num_channels; for (int yi = 0; yi < _y_size; ++yi) { int source_yi = _y_size - 1 - yi; int start = source_yi * row_size; reversed.insert(reversed.end(), _table.begin() + start, _table.begin() + start + row_size); } nassertv(reversed.size() <= _table.size()); // Also add in the extra buffer at the end. reversed.insert(reversed.end(), _table.size() - reversed.size(), (PN_float32)0.0); _table.swap(reversed); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::flip // Access: Published // Description: Reverses, transposes, and/or rotates the table // in-place according to the specified parameters. If // flip_x is true, the x axis is reversed; if flip_y is // true, the y axis is reversed. Then, if transpose is // true, the x and y axes are exchanged. These // parameters can be used to select any combination of // 90-degree or 180-degree rotations and flips. //////////////////////////////////////////////////////////////////// void PfmFile:: flip(bool flip_x, bool flip_y, bool transpose) { nassertv(is_valid()); Table flipped; flipped.reserve(_table.size()); if (transpose) { // Transposed case. X becomes Y, Y becomes X. for (int xi = 0; xi < _x_size; ++xi) { int source_xi = !flip_x ? xi : _x_size - 1 - xi; for (int yi = 0; yi < _y_size; ++yi) { int source_yi = !flip_y ? yi : _y_size - 1 - yi; const PN_float32 *p = &_table[(source_yi * _x_size + source_xi) * _num_channels]; for (int ci = 0; ci < _num_channels; ++ci) { flipped.push_back(p[ci]); } } } int t = _x_size; _x_size = _y_size; _y_size = t; } else { // Non-transposed. X is X, Y is Y. for (int yi = 0; yi < _y_size; ++yi) { int source_yi = !flip_y ? yi : _y_size - 1 - yi; for (int xi = 0; xi < _x_size; ++xi) { int source_xi = !flip_x ? xi : _x_size - 1 - xi; const PN_float32 *p = &_table[(source_yi * _x_size + source_xi) * _num_channels]; for (int ci = 0; ci < _num_channels; ++ci) { flipped.push_back(p[ci]); } } } } nassertv(flipped.size() <= _table.size()); // Also add in the extra buffer at the end. flipped.insert(flipped.end(), _table.size() - flipped.size(), (PN_float32)0.0); _table.swap(flipped); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::xform // Access: Published // Description: Applies the indicated transform matrix to all points // in-place. //////////////////////////////////////////////////////////////////// void PfmFile:: xform(const LMatrix4f &transform) { nassertv(is_valid()); for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { if (!has_point(xi, yi)) { continue; } LPoint3f &p = modify_point(xi, yi); transform.xform_point_general_in_place(p); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::forward_distort // Access: Published // Description: Applies the distortion indicated in the supplied dist // map to the current map. The dist map is understood // to be a mapping of points in the range 0..1 in the // first two dimensions. Each (u,v) point in the // current file is replaced with the point from the same // file at (x,y), where (x,y) is the point value from // the dist map. // // By convention, the y axis in inverted in the // distortion map relative to the coordinates here. A y // value of 0 in the distortion map corresponds with a v // value of 1 in this file. // // If scale_factor is not 1, it should be a value > 1, // and it specifies the factor to upscale the working // table while processing, to reduce artifacts from // integer truncation. //////////////////////////////////////////////////////////////////// void PfmFile:: forward_distort(const PfmFile &dist, PN_float32 scale_factor) { int working_x_size = (int)cceil(_x_size * scale_factor); int working_y_size = (int)cceil(_y_size * scale_factor); working_x_size = max(working_x_size, dist.get_x_size()); working_y_size = max(working_y_size, dist.get_y_size()); const PfmFile *source_p = this; PfmFile scaled_source; if ((*this).get_x_size() != working_x_size || (*this).get_y_size() != working_y_size) { // Rescale the source file as needed. scaled_source = (*this); scaled_source.resize(working_x_size, working_y_size); source_p = &scaled_source; } const PfmFile *dist_p = &dist; PfmFile scaled_dist; if (dist.get_x_size() != working_x_size || dist.get_y_size() != working_y_size) { // Rescale the dist file as needed. scaled_dist = dist; scaled_dist.resize(working_x_size, working_y_size); dist_p = &scaled_dist; } PfmFile result; result.clear(working_x_size, working_y_size, _num_channels); if (_has_no_data_value) { result.set_no_data_value(_no_data_value); result.fill(_no_data_value); } for (int yi = 0; yi < working_y_size; ++yi) { for (int xi = 0; xi < working_x_size; ++xi) { if (!dist_p->has_point(xi, yi)) { continue; } LPoint2 uv = dist_p->get_point2(xi, yi); LPoint3 p; if (!calc_bilinear_point(p, uv[0], 1.0 - uv[1])) { continue; } result.set_point(xi, working_y_size - 1 - yi, p); } } // Resize to the target size for completion. result.resize(_x_size, _y_size); nassertv(result._table.size() == _table.size()); _table.swap(result._table); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::reverse_distort // Access: Published // Description: Applies the distortion indicated in the supplied dist // map to the current map. The dist map is understood // to be a mapping of points in the range 0..1 in the // first two dimensions. Each (x,y) point in the // current file is replaced with the point from the same // file at (u,v), where (x,y) is the point value from // the dist map. // // By convention, the y axis in inverted in the // distortion map relative to the coordinates here. A y // value of 0 in the distortion map corresponds with a v // value of 1 in this file. // // If scale_factor is not 1, it should be a value > 1, // and it specifies the factor to upscale the working // table while processing, to reduce artifacts from // integer truncation. //////////////////////////////////////////////////////////////////// void PfmFile:: reverse_distort(const PfmFile &dist, PN_float32 scale_factor) { int working_x_size = (int)cceil(_x_size * scale_factor); int working_y_size = (int)cceil(_y_size * scale_factor); working_x_size = max(working_x_size, dist.get_x_size()); working_y_size = max(working_y_size, dist.get_y_size()); const PfmFile *source_p = this; PfmFile scaled_source; if ((*this).get_x_size() != working_x_size || (*this).get_y_size() != working_y_size) { // Rescale the source file as needed. scaled_source = (*this); scaled_source.resize(working_x_size, working_y_size); source_p = &scaled_source; } const PfmFile *dist_p = &dist; PfmFile scaled_dist; if (dist.get_x_size() != working_x_size || dist.get_y_size() != working_y_size) { // Rescale the dist file as needed. scaled_dist = dist; scaled_dist.resize(working_x_size, working_y_size); dist_p = &scaled_dist; } PfmFile result; result.clear(working_x_size, working_y_size, _num_channels); if (_has_no_data_value) { result.set_no_data_value(_no_data_value); result.fill(_no_data_value); } for (int yi = 0; yi < working_y_size; ++yi) { for (int xi = 0; xi < working_x_size; ++xi) { if (!source_p->has_point(xi, working_y_size - 1 - yi)) { continue; } if (!dist_p->has_point(xi, yi)) { continue; } LPoint2f uv = dist_p->get_point2(xi, yi); int dist_xi = (int)cfloor(uv[0] * (PN_float32)working_x_size); int dist_yi = (int)cfloor(uv[1] * (PN_float32)working_y_size); if (dist_xi < 0 || dist_xi >= working_x_size || dist_yi < 0 || dist_yi >= working_y_size) { continue; } /* if (!source_p->has_point(dist_xi, working_y_size - 1 - dist_yi)) { continue; } */ result.set_point(dist_xi, working_y_size - 1 - dist_yi, source_p->get_point(xi, working_y_size - 1 - yi)); } } // Resize to the target size for completion. result.resize(_x_size, _y_size); nassertv(result._table.size() == _table.size()); _table.swap(result._table); } //////////////////////////////////////////////////////////////////// // Function: PfmFile::merge // Access: Published // Description: Wherever there is missing data in this PfmFile (that // is, wherever has_point() returns false), copy data // from the other PfmFile, which must be exactly the // same dimensions as this one. //////////////////////////////////////////////////////////////////// void PfmFile:: merge(const PfmFile &other) { nassertv(is_valid() && other.is_valid()); nassertv(other._x_size == _x_size && other._y_size == _y_size); if (!_has_no_data_value) { // Trivial no-op. return; } for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { if (!has_point(xi, yi) && other.has_point(xi, yi)) { set_point(xi, yi, other.get_point(xi, yi)); } } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::copy_channel // Access: Published // Description: Copies just the specified channel values from the // indicated PfmFile (which could be same as this // PfmFile) into the specified channel of this one. //////////////////////////////////////////////////////////////////// void PfmFile:: copy_channel(int to_channel, const PfmFile &other, int from_channel) { nassertv(is_valid() && other.is_valid()); nassertv(other._x_size == _x_size && other._y_size == _y_size); nassertv(to_channel >= 0 && to_channel < get_num_channels() && from_channel >= 0 && from_channel < other.get_num_channels()); for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { set_component(xi, yi, to_channel, other.get_component(xi, yi, from_channel)); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::apply_crop // Access: Published // Description: Reduces the PFM file to the cells in the rectangle // bounded by (x_begin, x_end, y_begin, y_end), where // the _end cells are not included. //////////////////////////////////////////////////////////////////// void PfmFile:: apply_crop(int x_begin, int x_end, int y_begin, int y_end) { nassertv(x_begin >= 0 && x_begin <= x_end && x_end <= _x_size); nassertv(y_begin >= 0 && y_begin <= y_end && y_end <= _y_size); int new_x_size = x_end - x_begin; int new_y_size = y_end - y_begin; Table new_table; int new_size = new_x_size * new_y_size * _num_channels; // We allocate a little bit bigger to allow safe overflow: you can // call get_point3() or get_point4() on the last point of a 1- or // 3-channel image. new_table.insert(new_table.end(), new_size + 4, (PN_float32)0.0); for (int yi = 0; yi < new_y_size; ++yi) { memcpy(&new_table[(yi * new_x_size) * _num_channels], &_table[((yi + y_begin) * _x_size + x_begin) * _num_channels], new_x_size * sizeof(PN_float32) * _num_channels); } nassertv(new_table.size() == new_size + 4); _table.swap(new_table); _x_size = new_x_size; _y_size = new_y_size; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::clear_to_texcoords // Access: Published // Description: Replaces this PfmFile with a new PfmFile of size // x_size x y_size x 3, containing the x y 0 values in // the range 0 .. 1 according to the x y index. //////////////////////////////////////////////////////////////////// void PfmFile:: clear_to_texcoords(int x_size, int y_size) { clear(x_size, y_size, 3); LPoint2f uv_scale(1.0, 1.0); if (_x_size > 0) { uv_scale[0] = 1.0f / PN_float32(_x_size); } if (_y_size > 0) { uv_scale[1] = 1.0f / PN_float32(_y_size); } for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { LPoint3f uv((PN_float32(xi) + 0.5) * uv_scale[0], (PN_float32(yi) + 0.5) * uv_scale[1], 0.0f); set_point(xi, yi, uv); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::calc_tight_bounds // Access: Published // Description: Calculates the minimum and maximum vertices of all // points within the table. Assumes the table contains // 3-D points. // // The return value is true if any points in the table, // or false if none are. //////////////////////////////////////////////////////////////////// bool PfmFile:: calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point) const { min_point.set(0.0f, 0.0f, 0.0f); max_point.set(0.0f, 0.0f, 0.0f); bool found_any = false; for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { if (!has_point(xi, yi)) { continue; } const LPoint3f &point = get_point(xi, yi); if (!found_any) { min_point = point; max_point = point; found_any = true; } else { min_point.set(min(min_point[0], point[0]), min(min_point[0], point[0]), min(min_point[0], point[0])); max_point.set(max(max_point[0], point[0]), max(max_point[0], point[0]), max(max_point[0], point[0])); } } } return found_any; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::compute_planar_bounds // Access: Published // Description: Computes the minmax bounding volume of the points in // 3-D space, assuming the points represent a // mostly-planar surface. // // This algorithm works by sampling the (square) // sample_radius pixels at the four point_dist corners // around the center (cx - pd, cx + pd) and so on, to // approximate the plane of the surface. Then all of // the points are projected into that plane and the // bounding volume of the entire mesh within that plane // is determined. If points_only is true, the bounding // volume of only those four points is determined. // // center, point_dist and sample_radius are in UV space, // i.e. in the range 0..1. //////////////////////////////////////////////////////////////////// PT(BoundingHexahedron) PfmFile:: compute_planar_bounds(const LPoint2f ¢er, PN_float32 point_dist, PN_float32 sample_radius, bool points_only) const { LPoint3f p0, p1, p2, p3; compute_sample_point(p0, center[0] + point_dist, center[1] - point_dist, sample_radius); compute_sample_point(p1, center[0] + point_dist, center[1] + point_dist, sample_radius); compute_sample_point(p2, center[0] - point_dist, center[1] + point_dist, sample_radius); compute_sample_point(p3, center[0] - point_dist, center[1] - point_dist, sample_radius); LPoint3f normal; normal[0] = p0[1] * p1[2] - p0[2] * p1[1]; normal[1] = p0[2] * p1[0] - p0[0] * p1[2]; normal[2] = p0[0] * p1[1] - p0[1] * p1[0]; normal[0] += p1[1] * p2[2] - p1[2] * p2[1]; normal[1] += p1[2] * p2[0] - p1[0] * p2[2]; normal[2] += p1[0] * p2[1] - p1[1] * p2[0]; normal[0] += p2[1] * p3[2] - p2[2] * p3[1]; normal[1] += p2[2] * p3[0] - p2[0] * p3[2]; normal[2] += p2[0] * p3[1] - p2[1] * p3[0]; normal[0] += p3[1] * p0[2] - p3[2] * p0[1]; normal[1] += p3[2] * p0[0] - p3[0] * p0[2]; normal[2] += p3[0] * p0[1] - p3[1] * p0[0]; normal.normalize(); LVector3f up = (p1 - p0) + (p2 - p3); LPoint3f pcenter = ((p0 + p1 + p2 + p3) * 0.25); // Compute the transform necessary to rotate all of the points into // the Y = 0 plane. LMatrix4f rotate; look_at(rotate, normal, up); LMatrix4f rinv; rinv.invert_from(rotate); LPoint3f trans = pcenter * rinv; rinv.set_row(3, -trans); rotate.invert_from(rinv); // Now determine the minmax. PN_float32 min_x, min_y, min_z, max_x, max_y, max_z; bool got_point = false; if (points_only) { LPoint3f points[4] = { p0 * rinv, p1 * rinv, p2 * rinv, p3 * rinv, }; for (int i = 0; i < 4; ++i) { const LPoint3f &point = points[i]; if (!got_point) { min_x = point[0]; min_y = point[1]; min_z = point[2]; max_x = point[0]; max_y = point[1]; max_z = point[2]; got_point = true; } else { min_x = min(min_x, point[0]); min_y = min(min_y, point[1]); min_z = min(min_z, point[2]); max_x = max(max_x, point[0]); max_y = max(max_y, point[1]); max_z = max(max_z, point[2]); } } } else { for (int yi = 0; yi < _y_size; ++yi) { for (int xi = 0; xi < _x_size; ++xi) { if (!has_point(xi, yi)) { continue; } LPoint3f point = get_point(xi, yi) * rinv; if (!got_point) { min_x = point[0]; min_y = point[1]; min_z = point[2]; max_x = point[0]; max_y = point[1]; max_z = point[2]; got_point = true; } else { min_x = min(min_x, point[0]); min_y = min(min_y, point[1]); min_z = min(min_z, point[2]); max_x = max(max_x, point[0]); max_y = max(max_y, point[1]); max_z = max(max_z, point[2]); } } } } PT(BoundingHexahedron) bounds; // We create a BoundingHexahedron with the points in a particular // well-defined order, based on the current coordinate system. CoordinateSystem cs = get_default_coordinate_system(); switch (cs) { case CS_yup_right: bounds = new BoundingHexahedron (LPoint3(min_x, min_y, min_z), LPoint3(max_x, min_y, min_z), LPoint3(min_x, max_y, min_z), LPoint3(max_x, max_y, min_z), LPoint3(min_x, min_y, max_z), LPoint3(max_x, min_y, max_z), LPoint3(min_x, max_y, max_z), LPoint3(max_x, max_y, max_z)); break; case CS_zup_right: bounds = new BoundingHexahedron (LPoint3(min_x, min_y, min_z), LPoint3(max_x, min_y, min_z), LPoint3(min_x, min_y, max_z), LPoint3(max_x, min_y, max_z), LPoint3(min_x, max_y, min_z), LPoint3(max_x, max_y, min_z), LPoint3(min_x, max_y, max_z), LPoint3(max_x, max_y, max_z)); break; case CS_yup_left: bounds = new BoundingHexahedron (LPoint3(max_x, min_y, max_z), LPoint3(min_x, min_y, max_z), LPoint3(max_x, max_y, max_z), LPoint3(min_x, max_y, max_z), LPoint3(max_x, min_y, min_z), LPoint3(min_x, min_y, min_z), LPoint3(max_x, max_y, min_z), LPoint3(min_x, max_y, min_z)); break; case CS_zup_left: bounds = new BoundingHexahedron (LPoint3(max_x, max_y, min_z), LPoint3(min_x, max_y, min_z), LPoint3(max_x, max_y, max_z), LPoint3(min_x, max_y, max_z), LPoint3(max_x, min_y, min_z), LPoint3(min_x, min_y, min_z), LPoint3(max_x, min_y, max_z), LPoint3(min_x, min_y, max_z)); break; } // Rotate the bounding volume back into the original space of the // screen. bounds->xform(LCAST(PN_stdfloat, rotate)); return bounds; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::compute_sample_point // Access: Published // Description: Computes the average of all the point within // sample_radius (manhattan distance) and the indicated // point. // // The point coordinates are given in UV space, in the // range 0..1. //////////////////////////////////////////////////////////////////// void PfmFile:: compute_sample_point(LPoint3f &result, PN_float32 x, PN_float32 y, PN_float32 sample_radius) const { x *= _x_size; y *= _y_size; PN_float32 xr = sample_radius * _x_size; PN_float32 yr = sample_radius * _y_size; switch (_num_channels) { case 1: { PN_float32 result1; box_filter_region(result1, x - xr, y - yr, x + xr, y + yr); result.set(result1, 0.0, 0.0); } break; case 3: box_filter_region(result, x - xr, y - yr, x + xr, y + yr); break; case 4: { LPoint4f result4; box_filter_region(result4, x - xr, y - yr, x + xr, y + yr); result.set(result4[0], result4[1], result4[2]); } break; default: nassertv(false); } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::copy_sub_image // Access: Published // Description: Copies a rectangular area of another image into a // rectangular area of this image. Both images must // already have been initialized. The upper-left corner // of the region in both images is specified, and the // size of the area; if the size is omitted, it defaults // to the entire other image, or the largest piece that // will fit. //////////////////////////////////////////////////////////////////// void PfmFile:: copy_sub_image(const PfmFile ©, int xto, int yto, int xfrom, int yfrom, int x_size, int y_size) { int xmin, ymin, xmax, ymax; setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size, xmin, ymin, xmax, ymax); int x, y; switch (_num_channels) { case 1: { for (y = ymin; y < ymax; y++) { for (x = xmin; x < xmax; x++) { set_point1(x, y, copy.get_point1(x - xmin + xfrom, y - ymin + yfrom)); } } } break; case 2: { for (y = ymin; y < ymax; y++) { for (x = xmin; x < xmax; x++) { set_point2(x, y, copy.get_point2(x - xmin + xfrom, y - ymin + yfrom)); } } } break; case 3: { for (y = ymin; y < ymax; y++) { for (x = xmin; x < xmax; x++) { set_point(x, y, copy.get_point(x - xmin + xfrom, y - ymin + yfrom)); } } } break; case 4: { for (y = ymin; y < ymax; y++) { for (x = xmin; x < xmax; x++) { set_point4(x, y, copy.get_point4(x - xmin + xfrom, y - ymin + yfrom)); } } } break; } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::output // Access: Published // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: output(ostream &out) const { out << "floating-point image: " << _x_size << " by " << _y_size << " pixels, " << _num_channels << " channels."; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_region // Access: Private // Description: Averages all the points in the rectangle from x0 // .. y0 to x1 .. y1 into result. The region may be // defined by floating-point boundaries; the result will // be weighted by the degree of coverage of each // included point. //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_region(PN_float32 &result, PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const { result = 0.0; PN_float32 coverage = 0.0; if (x1 < x0 || y1 < y0) { return; } nassertv(y0 >= 0.0 && y1 >= 0.0); int y = (int)y0; // Get the first (partial) row box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0); int y_last = (int)y1; if (y < y_last) { y++; while (y < y_last) { // Get each consecutive (complete) row box_filter_line(result, coverage, x0, y, x1, 1.0); y++; } // Get the final (partial) row PN_float32 y_contrib = y1 - (PN_float32)y_last; if (y_contrib > 0.0001) { box_filter_line(result, coverage, x0, y, x1, y_contrib); } } if (coverage != 0.0) { result /= coverage; } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_region // Access: Private // Description: Averages all the points in the rectangle from x0 // .. y0 to x1 .. y1 into result. The region may be // defined by floating-point boundaries; the result will // be weighted by the degree of coverage of each // included point. //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_region(LPoint3f &result, PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const { result = LPoint3f::zero(); PN_float32 coverage = 0.0; if (x1 < x0 || y1 < y0) { return; } nassertv(y0 >= 0.0 && y1 >= 0.0); int y = (int)y0; // Get the first (partial) row box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0); int y_last = (int)y1; if (y < y_last) { y++; while (y < y_last) { // Get each consecutive (complete) row box_filter_line(result, coverage, x0, y, x1, 1.0); y++; } // Get the final (partial) row PN_float32 y_contrib = y1 - (PN_float32)y_last; if (y_contrib > 0.0001) { box_filter_line(result, coverage, x0, y, x1, y_contrib); } } if (coverage != 0.0) { result /= coverage; } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_region // Access: Private // Description: Averages all the points in the rectangle from x0 // .. y0 to x1 .. y1 into result. The region may be // defined by floating-point boundaries; the result will // be weighted by the degree of coverage of each // included point. //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_region(LPoint4f &result, PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const { result = LPoint4f::zero(); PN_float32 coverage = 0.0; if (x1 < x0 || y1 < y0) { return; } nassertv(y0 >= 0.0 && y1 >= 0.0); int y = (int)y0; // Get the first (partial) row box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0); int y_last = (int)y1; if (y < y_last) { y++; while (y < y_last) { // Get each consecutive (complete) row box_filter_line(result, coverage, x0, y, x1, 1.0); y++; } // Get the final (partial) row PN_float32 y_contrib = y1 - (PN_float32)y_last; if (y_contrib > 0.0001) { box_filter_line(result, coverage, x0, y, x1, y_contrib); } } if (coverage != 0.0) { result /= coverage; } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_line // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_line(PN_float32 &result, PN_float32 &coverage, PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const { int x = (int)x0; // Get the first (partial) xel box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib); int x_last = (int)x1; if (x < x_last) { x++; while (x < x_last) { // Get each consecutive (complete) xel box_filter_point(result, coverage, x, y, 1.0, y_contrib); x++; } // Get the final (partial) xel PN_float32 x_contrib = x1 - (PN_float32)x_last; if (x_contrib > 0.0001) { box_filter_point(result, coverage, x, y, x_contrib, y_contrib); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_line // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_line(LPoint3f &result, PN_float32 &coverage, PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const { int x = (int)x0; // Get the first (partial) xel box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib); int x_last = (int)x1; if (x < x_last) { x++; while (x < x_last) { // Get each consecutive (complete) xel box_filter_point(result, coverage, x, y, 1.0, y_contrib); x++; } // Get the final (partial) xel PN_float32 x_contrib = x1 - (PN_float32)x_last; if (x_contrib > 0.0001) { box_filter_point(result, coverage, x, y, x_contrib, y_contrib); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_line // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_line(LPoint4f &result, PN_float32 &coverage, PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const { int x = (int)x0; // Get the first (partial) xel box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib); int x_last = (int)x1; if (x < x_last) { x++; while (x < x_last) { // Get each consecutive (complete) xel box_filter_point(result, coverage, x, y, 1.0, y_contrib); x++; } // Get the final (partial) xel PN_float32 x_contrib = x1 - (PN_float32)x_last; if (x_contrib > 0.0001) { box_filter_point(result, coverage, x, y, x_contrib, y_contrib); } } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_point // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_point(PN_float32 &result, PN_float32 &coverage, int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const { if (!has_point(x, y)) { return; } PN_float32 point = get_point1(x, y); PN_float32 contrib = x_contrib * y_contrib; result += point * contrib; coverage += contrib; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_point // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_point(LPoint3f &result, PN_float32 &coverage, int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const { if (!has_point(x, y)) { return; } const LPoint3f &point = get_point(x, y); PN_float32 contrib = x_contrib * y_contrib; result += point * contrib; coverage += contrib; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::box_filter_point // Access: Private // Description: //////////////////////////////////////////////////////////////////// void PfmFile:: box_filter_point(LPoint4f &result, PN_float32 &coverage, int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const { if (!has_point(x, y)) { return; } const LPoint4f &point = get_point4(x, y); PN_float32 contrib = x_contrib * y_contrib; result += point * contrib; coverage += contrib; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::fill_mini_grid // Access: Private // Description: A support function for calc_average_point(), this // recursively fills in the holes in the mini_grid data // with the index to the nearest value. //////////////////////////////////////////////////////////////////// void PfmFile:: fill_mini_grid(MiniGridCell *mini_grid, int x_size, int y_size, int xi, int yi, int dist, int sxi, int syi) const { if (xi < 0 || xi >= x_size || yi < 0 || yi >= y_size) { // Out of bounds. return; } int gi = yi * x_size + xi; if (mini_grid[gi]._dist == -1 || mini_grid[gi]._dist > dist) { // Here's an undefined value that we need to populate. mini_grid[gi]._dist = dist; mini_grid[gi]._sxi = sxi; mini_grid[gi]._syi = syi; fill_mini_grid(mini_grid, x_size, y_size, xi + 1, yi, dist + 1, sxi, syi); fill_mini_grid(mini_grid, x_size, y_size, xi - 1, yi, dist + 1, sxi, syi); fill_mini_grid(mini_grid, x_size, y_size, xi, yi + 1, dist + 1, sxi, syi); fill_mini_grid(mini_grid, x_size, y_size, xi, yi - 1, dist + 1, sxi, syi); } } //////////////////////////////////////////////////////////////////// // Function: PfmFile::has_point_noop // Access: Private, Static // Description: The implementation of has_point() for // files without a no_data_value. //////////////////////////////////////////////////////////////////// bool PfmFile:: has_point_noop(const PfmFile *self, int x, int y) { if ((x >= 0 && x < self->_x_size) && (y >= 0 && y < self->_y_size)) { return true; } return false; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::has_point_1 // Access: Private, Static // Description: The implementation of has_point() for 1-component // files with a no_data_value. //////////////////////////////////////////////////////////////////// bool PfmFile:: has_point_1(const PfmFile *self, int x, int y) { if ((x >= 0 && x < self->_x_size) && (y >= 0 && y < self->_y_size)) { return self->_table[(y * self->_x_size + x)] != self->_no_data_value[0]; } return false; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::has_point_3 // Access: Private, Static // Description: The implementation of has_point() for 3-component // files with a no_data_value. //////////////////////////////////////////////////////////////////// bool PfmFile:: has_point_3(const PfmFile *self, int x, int y) { if ((x >= 0 && x < self->_x_size) && (y >= 0 && y < self->_y_size)) { return *(LPoint3f *)&self->_table[(y * self->_x_size + x) * 3] != *(LPoint3f *)&self->_no_data_value; } return false; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::has_point_4 // Access: Private, Static // Description: The implementation of has_point() for 4-component // files with a no_data_value. //////////////////////////////////////////////////////////////////// bool PfmFile:: has_point_4(const PfmFile *self, int x, int y) { if ((x >= 0 && x < self->_x_size) && (y >= 0 && y < self->_y_size)) { return *(LPoint4f *)&self->_table[(y * self->_x_size + x) * 4] != *(LPoint4f *)&self->_no_data_value; } return false; } //////////////////////////////////////////////////////////////////// // Function: PfmFile::has_point_chan4 // Access: Private, Static // Description: The implementation of has_point() for 4-component // files with set_no_data_chan4() in effect. This means // that the data is valid iff the fourth channel >= 0. //////////////////////////////////////////////////////////////////// bool PfmFile:: has_point_chan4(const PfmFile *self, int x, int y) { if ((x >= 0 && x < self->_x_size) && (y >= 0 && y < self->_y_size)) { return self->_table[(y * self->_x_size + x) * 4 + 3] >= 0.0; } return false; }