Panda3D
pfmFile.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pfmFile.cxx
10  * @author drose
11  * @date 2010-12-23
12  */
13 
14 #include "config_pnmimage.h"
15 #include "pfmFile.h"
16 #include "virtualFileSystem.h"
17 #include "pandaFileStream.h"
18 #include "littleEndian.h"
19 #include "bigEndian.h"
20 #include "cmath.h"
21 #include "pnmImage.h"
22 #include "pnmReader.h"
23 #include "pnmWriter.h"
24 #include "string_utils.h"
25 #include "look_at.h"
26 
27 using std::istream;
28 using std::max;
29 using std::min;
30 using std::ostream;
31 
32 /**
33  *
34  */
35 PfmFile::
36 PfmFile() {
37  _has_no_data_value = false;
38  _has_no_data_threshold = false;
39  _no_data_value = LPoint4f::zero();
40  _has_point = has_point_noop;
41  clear();
42 }
43 
44 /**
45  *
46  */
47 PfmFile::
48 PfmFile(const PfmFile &copy) :
49  PNMImageHeader(copy),
50  _table(copy._table),
51  _scale(copy._scale),
52  _has_no_data_value(copy._has_no_data_value),
53  _has_no_data_threshold(copy._has_no_data_threshold),
54  _no_data_value(copy._no_data_value),
55  _has_point(copy._has_point)
56 {
57 }
58 
59 /**
60  *
61  */
62 void PfmFile::
63 operator = (const PfmFile &copy) {
64  PNMImageHeader::operator = (copy);
65  _table = copy._table;
66  _scale = copy._scale;
67  _has_no_data_value = copy._has_no_data_value;
68  _has_no_data_threshold = copy._has_no_data_threshold;
69  _no_data_value = copy._no_data_value;
70  _has_point = copy._has_point;
71 }
72 
73 /**
74  * Eliminates all data in the file.
75  */
76 void PfmFile::
77 clear() {
78  _x_size = 0;
79  _y_size = 0;
80  _scale = 1.0;
81  _num_channels = 0;
82  _table.clear();
84 }
85 
86 /**
87  * Resets to an empty table with a specific size. The case of num_channels ==
88  * 0 is allowed only in the case that x_size and y_size are also == 0; and
89  * this makes an empty (and invalid) PfmFile.
90  */
91 void PfmFile::
92 clear(int x_size, int y_size, int num_channels) {
93  nassertv(x_size >= 0 && y_size >= 0);
94  nassertv((num_channels > 0 && num_channels <= 4) ||
95  (x_size == 0 && y_size == 0 && num_channels == 0));
96 
97  _x_size = x_size;
98  _y_size = y_size;
99  _scale = 1.0;
100  _num_channels = num_channels;
101 
102  _table.clear();
103  int size = _x_size * _y_size * _num_channels;
104 
105  // We allocate a little bit bigger to allow safe overflow: you can call
106  // get_point3() or get_point4() on the last point of a 1- or 3-channel
107  // image.
108  _table.insert(_table.end(), size + 4, (PN_float32)0.0);
109 
111 }
112 
113 /**
114  * Reads the PFM data from the indicated file, returning true on success,
115  * false on failure.
116  *
117  * This can also handle reading a standard image file supported by PNMImage;
118  * it will be quietly converted to a floating-point type.
119  */
120 bool PfmFile::
121 read(const Filename &fullpath) {
123 
124  Filename filename = Filename::binary_filename(fullpath);
125  PT(VirtualFile) file = vfs->get_file(filename);
126  if (file == nullptr) {
127  // No such file.
128  pnmimage_cat.error()
129  << "Could not find " << fullpath << "\n";
130  return false;
131  }
132 
133  if (pnmimage_cat.is_debug()) {
134  pnmimage_cat.debug()
135  << "Reading PFM file " << filename << "\n";
136  }
137 
138  istream *in = file->open_read_file(true);
139  bool success = read(*in, fullpath);
140  vfs->close_read_file(in);
141 
142  return success;
143 }
144 
145 /**
146  * Reads the PFM data from the indicated stream, returning true on success,
147  * false on failure.
148  *
149  * This can also handle reading a standard image file supported by PNMImage;
150  * it will be quietly converted to a floating-point type.
151  */
152 bool PfmFile::
153 read(istream &in, const Filename &fullpath) {
154  PNMReader *reader = make_reader(&in, false, fullpath);
155  if (reader == nullptr) {
156  clear();
157  return false;
158  }
159  return read(reader);
160 }
161 
162 /**
163  * Reads the PFM data using the indicated PNMReader.
164  *
165  * The PNMReader is always deleted upon completion, whether successful or not.
166  */
167 bool PfmFile::
168 read(PNMReader *reader) {
169  clear();
170 
171  if (reader == nullptr) {
172  return false;
173  }
174 
175  if (!reader->is_valid()) {
176  delete reader;
177  return false;
178  }
179 
180  if (!reader->is_floating_point()) {
181  // Not a floating-point file. Quietly convert it.
182  PNMImage pnm;
183  if (!pnm.read(reader)) {
184  return false;
185  }
186 
187  return load(pnm);
188  }
189 
190  bool success = reader->read_pfm(*this);
191  delete reader;
192  return success;
193 }
194 
195 /**
196  * Writes the PFM data to the indicated file, returning true on success, false
197  * on failure.
198  *
199  * If the type implied by the filename extension supports floating-point, the
200  * data will be written directly; otherwise, the floating-point data will be
201  * quietly converted to the appropriate integer type.
202  */
203 bool PfmFile::
204 write(const Filename &fullpath) {
205  if (!is_valid()) {
206  pnmimage_cat.error()
207  << "PFM file is invalid.\n";
208  return false;
209  }
210 
211  Filename filename = Filename::binary_filename(fullpath);
212  pofstream out;
213  if (!filename.open_write(out)) {
214  pnmimage_cat.error()
215  << "Unable to open " << filename << "\n";
216  return false;
217  }
218 
219  if (pnmimage_cat.is_debug()) {
220  pnmimage_cat.debug()
221  << "Writing PFM file " << filename << "\n";
222  }
223 
224  return write(out, fullpath);
225 }
226 
227 /**
228  * Writes the PFM data to the indicated stream, returning true on success,
229  * false on failure.
230  */
231 bool PfmFile::
232 write(ostream &out, const Filename &fullpath) {
233  if (!is_valid()) {
234  return false;
235  }
236 
237  PNMWriter *writer = make_writer(&out, false, fullpath);
238  if (writer == nullptr) {
239  return false;
240  }
241 
242  return write(writer);
243 }
244 
245 /**
246  * Writes the PFM data using the indicated PNMWriter.
247  *
248  * The PNMWriter is always deleted upon completion, whether successful or not.
249  */
250 bool PfmFile::
251 write(PNMWriter *writer) {
252  if (writer == nullptr) {
253  return false;
254  }
255 
256  if (!is_valid()) {
257  delete writer;
258  return false;
259  }
260 
261  writer->copy_header_from(*this);
262 
263  if (!writer->supports_floating_point()) {
264  // Hmm, it's an integer file type. Convert it from the floating-point
265  // data we have.
266  PNMImage pnmimage;
267  if (!store(pnmimage)) {
268  delete writer;
269  return false;
270  }
271  writer->copy_header_from(pnmimage);
272  bool success = (writer->write_data(pnmimage.get_array(), pnmimage.get_alpha_array()) != 0);
273  delete writer;
274  return success;
275  }
276 
277  bool success = writer->write_pfm(*this);
278  delete writer;
279  return success;
280 }
281 
282 /**
283  * Fills the PfmFile with the data from the indicated PNMImage, converted to
284  * floating-point values.
285  */
286 bool PfmFile::
287 load(const PNMImage &pnmimage) {
288  if (!pnmimage.is_valid()) {
289  clear();
290  return false;
291  }
292 
293  int num_channels = pnmimage.get_num_channels();
294 
295  clear(pnmimage.get_x_size(), pnmimage.get_y_size(), num_channels);
296  switch (num_channels) {
297  case 1:
298  {
299  for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) {
300  for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) {
301  _table[yi * _x_size + xi] = pnmimage.get_gray(xi, yi);
302  }
303  }
304  }
305  break;
306 
307  case 2:
308  {
309  for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) {
310  for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) {
311  PN_float32 *point = &_table[(yi * _x_size + xi) * _num_channels];
312  point[0] = pnmimage.get_gray(xi, yi);
313  point[1] = pnmimage.get_alpha(xi, yi);
314  }
315  }
316  }
317  break;
318 
319  case 3:
320  {
321  for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) {
322  for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) {
323  PN_float32 *point = &_table[(yi * _x_size + xi) * _num_channels];
324  LRGBColorf xel = pnmimage.get_xel(xi, yi);
325  point[0] = xel[0];
326  point[1] = xel[1];
327  point[2] = xel[2];
328  }
329  }
330  }
331  break;
332 
333  case 4:
334  {
335  for (int yi = 0; yi < pnmimage.get_y_size(); ++yi) {
336  for (int xi = 0; xi < pnmimage.get_x_size(); ++xi) {
337  PN_float32 *point = &_table[(yi * _x_size + xi) * _num_channels];
338  LColorf xel = pnmimage.get_xel_a(xi, yi);
339  point[0] = xel[0];
340  point[1] = xel[1];
341  point[2] = xel[2];
342  point[3] = xel[3];
343  }
344  }
345  }
346  break;
347 
348  default:
349  nassert_raise("unexpected channel count");
350  return false;
351  }
352  return true;
353 }
354 
355 
356 /**
357  * Copies the data to the indicated PNMImage, converting to RGB values.
358  */
359 bool PfmFile::
360 store(PNMImage &pnmimage) const {
361  if (!is_valid()) {
362  pnmimage.clear();
363  return false;
364  }
365 
366  int num_channels = get_num_channels();
367  pnmimage.clear(get_x_size(), get_y_size(), num_channels, PGM_MAXMAXVAL);
368  switch (num_channels) {
369  case 1:
370  {
371  for (int yi = 0; yi < get_y_size(); ++yi) {
372  for (int xi = 0; xi < get_x_size(); ++xi) {
373  pnmimage.set_gray(xi, yi, _table[yi * _x_size + xi]);
374  }
375  }
376  }
377  break;
378 
379  case 2:
380  {
381  for (int yi = 0; yi < get_y_size(); ++yi) {
382  for (int xi = 0; xi < get_x_size(); ++xi) {
383  const LPoint2f &point = get_point2(xi, yi);
384  pnmimage.set_gray(xi, yi, point[0]);
385  pnmimage.set_alpha(xi, yi, point[1]);
386  }
387  }
388  }
389  break;
390 
391  case 3:
392  {
393  for (int yi = 0; yi < get_y_size(); ++yi) {
394  for (int xi = 0; xi < get_x_size(); ++xi) {
395  const LPoint3f &point = get_point3(xi, yi);
396  pnmimage.set_xel(xi, yi, point[0], point[1], point[2]);
397  }
398  }
399  }
400  break;
401 
402  case 4:
403  {
404  for (int yi = 0; yi < get_y_size(); ++yi) {
405  for (int xi = 0; xi < get_x_size(); ++xi) {
406  const LPoint4f &point = get_point4(xi, yi);
407  pnmimage.set_xel_a(xi, yi, point[0], point[1], point[2], point[3]);
408  }
409  }
410  }
411  break;
412 
413  default:
414  nassert_raise("unexpected channel count");
415  return false;
416  }
417  return true;
418 }
419 
420 /**
421  * Stores 1 or 0 values into the indicated PNMImage, according to has_point()
422  * for each pixel. Each valid point gets a 1 value; each nonexistent point
423  * gets a 0 value.
424  */
425 bool PfmFile::
426 store_mask(PNMImage &pnmimage) const {
427  if (!is_valid()) {
428  pnmimage.clear();
429  return false;
430  }
431 
432  pnmimage.clear(get_x_size(), get_y_size(), 1, 255);
433  for (int yi = 0; yi < get_y_size(); ++yi) {
434  for (int xi = 0; xi < get_x_size(); ++xi) {
435  pnmimage.set_gray(xi, yi, has_point(xi, yi));
436  }
437  }
438  return true;
439 }
440 
441 /**
442  * Stores 1 or 0 values into the indicated PNMImage, according to has_point()
443  * for each pixel. Each valid point gets a 1 value; each nonexistent point
444  * gets a 0 value.
445  *
446  * This flavor of store_mask also checks whether the valid points are within
447  * the specified min/max range. Any valid points without the condition
448  * min_point[c] <= value[c] <= max_point[c], for any c, are stored with a 0 in
449  * the mask.
450  */
451 bool PfmFile::
452 store_mask(PNMImage &pnmimage, const LVecBase4f &min_point, const LVecBase4f &max_point) const {
453  if (!is_valid()) {
454  pnmimage.clear();
455  return false;
456  }
457 
458  pnmimage.clear(get_x_size(), get_y_size(), 1, 255);
459  for (int yi = 0; yi < get_y_size(); ++yi) {
460  for (int xi = 0; xi < get_x_size(); ++xi) {
461  if (!has_point(xi, yi)) {
462  pnmimage.set_gray(xi, yi, 0);
463  } else {
464  const float *value = &_table[(yi * _x_size + xi) * _num_channels];
465  bool in_range = true;
466  for (int ci = 0; ci < _num_channels; ++ci) {
467  if (value[ci] < min_point[ci] || value[ci] > max_point[ci]) {
468  in_range = false;
469  break;
470  }
471  }
472  pnmimage.set_gray(xi, yi, (float)in_range);
473  }
474  }
475  }
476  return true;
477 }
478 
479 /**
480  * Fills the table with all of the same value.
481  */
482 void PfmFile::
483 fill(const LPoint4f &value) {
484  switch (_num_channels) {
485  case 1:
486  {
487  for (int yi = 0; yi < _y_size; ++yi) {
488  for (int xi = 0; xi < _x_size; ++xi) {
489  _table[(yi * _x_size + xi)] = value[0];
490  }
491  }
492  }
493  break;
494 
495  case 2:
496  {
497  for (int yi = 0; yi < _y_size; ++yi) {
498  for (int xi = 0; xi < _x_size; ++xi) {
499  (*(LPoint2f *)&_table[(yi * _x_size + xi) * _num_channels]).set(value[0], value[1]);
500  }
501  }
502  }
503  break;
504 
505  case 3:
506  {
507  for (int yi = 0; yi < _y_size; ++yi) {
508  for (int xi = 0; xi < _x_size; ++xi) {
509  (*(LPoint3f *)&_table[(yi * _x_size + xi) * _num_channels]).set(value[0], value[1], value[2]);
510  }
511  }
512  }
513  break;
514 
515  case 4:
516  {
517  for (int yi = 0; yi < _y_size; ++yi) {
518  for (int xi = 0; xi < _x_size; ++xi) {
519  *(LPoint4f *)&_table[(yi * _x_size + xi) * _num_channels] = value;
520  }
521  }
522  }
523  break;
524  }
525 }
526 
527 /**
528  * Fills the table with all NaN.
529  */
530 void PfmFile::
532  PN_float32 nan = make_nan((PN_float32)0.0);
533  LPoint4f nan4(nan, nan, nan, nan);
534  fill(nan4);
535 }
536 
537 /**
538  * Fills the table with the current no_data value, so that the table is empty.
539  */
540 void PfmFile::
542  fill(_no_data_value);
543 }
544 
545 /**
546  * Fills the indicated channel with all of the same value, leaving the other
547  * channels unchanged.
548  */
549 void PfmFile::
550 fill_channel(int channel, PN_float32 value) {
551  nassertv(channel >= 0 && channel < _num_channels);
552 
553  for (int yi = 0; yi < _y_size; ++yi) {
554  for (int xi = 0; xi < _x_size; ++xi) {
555  _table[(yi * _x_size + xi) * _num_channels + channel] = value;
556  }
557  }
558 }
559 
560 /**
561  * Fills the indicated channel with NaN, leaving the other channels unchanged.
562  */
563 void PfmFile::
564 fill_channel_nan(int channel) {
565  PN_float32 nan = make_nan((PN_float32)0.0);
566  fill_channel(channel, nan);
567 }
568 
569 /**
570  * Fills the indicated channel with all of the same value, but only where the
571  * table already has a data point. Leaves empty points unchanged.
572  */
573 void PfmFile::
574 fill_channel_masked(int channel, PN_float32 value) {
575  nassertv(channel >= 0 && channel < _num_channels);
576 
577  if (!_has_no_data_value) {
578  fill_channel(channel, value);
579  } else {
580  for (int yi = 0; yi < _y_size; ++yi) {
581  for (int xi = 0; xi < _x_size; ++xi) {
582  if (has_point(xi, yi)) {
583  _table[(yi * _x_size + xi) * _num_channels + channel] = value;
584  }
585  }
586  }
587  }
588 }
589 
590 /**
591  * Fills the indicated channel with NaN, but only where the table already has
592  * a data point. Leaves empty points unchanged.
593  */
594 void PfmFile::
596  PN_float32 nan = make_nan((PN_float32)0.0);
597  fill_channel_masked(channel, nan);
598 }
599 
600 /**
601  * Computes the unweighted average point of all points within the box centered
602  * at (x, y) with the indicated Manhattan-distance radius. Missing points are
603  * assigned the value of their nearest neighbor. Returns true if successful,
604  * or false if the point value cannot be determined.
605  */
606 bool PfmFile::
607 calc_average_point(LPoint3f &result, PN_float32 x, PN_float32 y, PN_float32 radius) const {
608  result = LPoint3f::zero();
609 
610  int min_x = int(ceil(x - radius));
611  int min_y = int(ceil(y - radius));
612  int max_x = int(floor(x + radius));
613  int max_y = int(floor(y + radius));
614 
615  // We first construct a mini-grid of x_size by y_size integer values to
616  // index into the main table. This indirection allows us to fill in the
617  // holes in the mini-grid with the nearest known values before we compute
618  // the average.
619  int x_size = max_x - min_x + 1;
620  int y_size = max_y - min_y + 1;
621  int size = x_size * y_size;
622  if (size == 0) {
623  return false;
624  }
625 
626  pvector<MiniGridCell> mini_grid;
627  mini_grid.insert(mini_grid.end(), size, MiniGridCell());
628 
629  // Now collect the known data points and apply them to the mini-grid.
630  min_x = max(min_x, 0);
631  min_y = max(min_y, 0);
632  max_x = min(max_x, _x_size - 1);
633  max_y = min(max_y, _y_size - 1);
634 
635  bool got_any = false;
636  int xi, yi;
637  for (yi = min_y; yi <= max_y; ++yi) {
638  for (xi = min_x; xi <= max_x; ++xi) {
639  if (!has_point(xi, yi)) {
640  continue;
641  }
642 
643  int gi = (yi - min_y) * y_size + (xi - min_x);
644  nassertr(gi >= 0 && gi < size, false);
645  mini_grid[gi]._sxi = xi;
646  mini_grid[gi]._syi = yi;
647  mini_grid[gi]._dist = 0;
648  got_any = true;
649  }
650  }
651 
652  if (!got_any) {
653  return false;
654  }
655 
656  // Now recursively fill in any missing holes in the mini-grid.
657  for (yi = 0; yi < y_size; ++yi) {
658  for (xi = 0; xi < x_size; ++xi) {
659  int gi = yi * x_size + xi;
660  if (mini_grid[gi]._dist == 0) {
661  int sxi = mini_grid[gi]._sxi;
662  int syi = mini_grid[gi]._syi;
663  fill_mini_grid(&mini_grid[0], x_size, y_size, xi + 1, yi, 1, sxi, syi);
664  fill_mini_grid(&mini_grid[0], x_size, y_size, xi - 1, yi, 1, sxi, syi);
665  fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi + 1, 1, sxi, syi);
666  fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi - 1, 1, sxi, syi);
667  }
668  }
669  }
670 
671  // Now the mini-grid is completely filled, so we can compute the average.
672  for (int gi = 0; gi < size; ++gi) {
673  int sxi = mini_grid[gi]._sxi;
674  int syi = mini_grid[gi]._syi;
675  const LPoint3f &p = get_point(sxi, syi);
676  result += p;
677  }
678 
679  result /= PN_float32(size);
680  return true;
681 }
682 
683 /**
684  * Computes the weighted average of the four nearest points to the floating-
685  * point index (x, y). Returns true if the point has any contributors, false
686  * if the point is unknown.
687  */
688 bool PfmFile::
689 calc_bilinear_point(LPoint3f &result, PN_float32 x, PN_float32 y) const {
690  result = LPoint3f::zero();
691 
692  x = (x * _x_size - 0.5);
693  y = (y * _y_size - 0.5);
694 
695  int min_x = int(floor(x));
696  int min_y = int(floor(y));
697 
698  PN_float32 frac_x = x - min_x;
699  PN_float32 frac_y = y - min_y;
700 
701  LPoint3f p00(LPoint3f::zero()), p01(LPoint3f::zero()), p10(LPoint3f::zero()), p11(LPoint3f::zero());
702  PN_float32 w00 = 0.0, w01 = 0.0, w10 = 0.0, w11 = 0.0;
703 
704  if (has_point(min_x, min_y)) {
705  w00 = (1.0 - frac_y) * (1.0 - frac_x);
706  p00 = get_point(min_x, min_y);
707  }
708  if (has_point(min_x + 1, min_y)) {
709  w10 = (1.0 - frac_y) * frac_x;
710  p10 = get_point(min_x + 1, min_y);
711  }
712  if (has_point(min_x, min_y + 1)) {
713  w01 = frac_y * (1.0 - frac_x);
714  p01 = get_point(min_x, min_y + 1);
715  }
716  if (has_point(min_x + 1, min_y + 1)) {
717  w11 = frac_y * frac_x;
718  p11 = get_point(min_x + 1, min_y + 1);
719  }
720 
721  PN_float32 net_w = w00 + w01 + w10 + w11;
722  if (net_w == 0.0) {
723  return false;
724  }
725 
726  result = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11) / net_w;
727  return true;
728 }
729 
730 /**
731  * Calculates the minimum and maximum x, y, and z depth component values,
732  * representing the bounding box of depth values, and places them in the
733  * indicated vectors. Returns true if successful, false if the mesh contains
734  * no points.
735  */
736 bool PfmFile::
737 calc_min_max(LVecBase3f &min_depth, LVecBase3f &max_depth) const {
738  bool any_points = false;
739 
740  min_depth = LVecBase3f::zero();
741  max_depth = LVecBase3f::zero();
742 
743  for (int yi = 0; yi < _y_size; ++yi) {
744  for (int xi = 0; xi < _x_size; ++xi) {
745  if (!has_point(xi, yi)) {
746  continue;
747  }
748 
749  const LPoint3f &p = get_point(xi, yi);
750  if (!any_points) {
751  min_depth = p;
752  max_depth = p;
753  any_points = true;
754  } else {
755  min_depth[0] = min(min_depth[0], p[0]);
756  min_depth[1] = min(min_depth[1], p[1]);
757  min_depth[2] = min(min_depth[2], p[2]);
758  max_depth[0] = max(max_depth[0], p[0]);
759  max_depth[1] = max(max_depth[1], p[1]);
760  max_depth[2] = max(max_depth[2], p[2]);
761  }
762  }
763  }
764 
765  return any_points;
766 }
767 
768 /**
769  * Computes the minimum range of x and y across the PFM file that include all
770  * points. If there are no points with no_data_value in the grid--that is,
771  * all points are included--then this will return (0, get_x_size(), 0,
772  * get_y_size()).
773  */
774 bool PfmFile::
775 calc_autocrop(int &x_begin, int &x_end, int &y_begin, int &y_end) const {
776  y_begin = 0;
777  while (is_row_empty(y_begin, 0, _x_size)) {
778  ++y_begin;
779  if (y_begin >= _y_size) {
780  // We've reached the end; the entire grid is empty.
781  x_begin = x_end = y_begin = y_end = 0;
782  return false;
783  }
784  }
785 
786  y_end = _y_size;
787  while (is_row_empty(y_end - 1, 0, _x_size)) {
788  --y_end;
789  nassertr(y_end > y_begin, false);
790  }
791 
792  // Now we've got the top and bottom bounds.
793  x_begin = 0;
794  while (is_column_empty(x_begin, y_begin, y_end)) {
795  ++x_begin;
796  nassertr(x_begin < _x_size, false);
797  }
798 
799  x_end = _x_size;
800  while (is_column_empty(x_end - 1, y_begin, y_end)) {
801  --x_end;
802  nassertr(x_end > x_begin, false);
803  }
804 
805  return true;
806 }
807 
808 /**
809  * Returns true if all of the points on row y, in the range [x_begin, x_end),
810  * are the no_data value, or false if any one of these points has a value.
811  */
812 bool PfmFile::
813 is_row_empty(int y, int x_begin, int x_end) const {
814  nassertr(y >= 0 && y < _y_size &&
815  x_begin >= 0 && x_begin <= x_end && x_end <= _x_size, false);
816 
817  if (!_has_no_data_value) {
818  return false;
819  }
820  for (int x = x_begin; x < x_end; ++x) {
821  if (has_point(x, y)) {
822  return false;
823  }
824  }
825 
826  return true;
827 }
828 
829 /**
830  * Returns true if all of the points on column x, from [y_begin, y_end), are
831  * the no_data value, or false if any one of these points has a value.
832  */
833 bool PfmFile::
834 is_column_empty(int x, int y_begin, int y_end) const {
835  nassertr(x >= 0 && x < _x_size &&
836  y_begin >= 0 && y_begin <= y_end && y_end <= _y_size, false);
837 
838  if (!_has_no_data_value) {
839  return false;
840  }
841  for (int y = y_begin; y < y_end; ++y) {
842  if (has_point(x, y)) {
843  return false;
844  }
845  }
846 
847  return true;
848 }
849 
850 /**
851  * Sets the no_data_nan flag. When num_channels is nonzero, then a NaN value
852  * in any of the first num_channels channels indicates no data for that point.
853  * If num_channels is zero, then all points are valid.
854  *
855  * This is a special case of set_no_data_value().
856  */
857 void PfmFile::
858 set_no_data_nan(int num_channels) {
859  if (num_channels > 0) {
860  num_channels = min(num_channels, _num_channels);
861  _has_no_data_value = true;
862  _has_no_data_threshold = false;
863  _no_data_value = LPoint4f::zero();
864  PN_float32 nan = make_nan((PN_float32)0.0);
865  for (int i = 0; i < num_channels; ++i) {
866  _no_data_value[i] = nan;
867  }
868  switch (num_channels) {
869  case 1:
870  _has_point = has_point_nan_1;
871  break;
872  case 2:
873  _has_point = has_point_nan_2;
874  break;
875  case 3:
876  _has_point = has_point_nan_3;
877  break;
878  case 4:
879  _has_point = has_point_nan_4;
880  break;
881  default:
882  nassert_raise("unexpected channel count");
883  break;
884  }
885  } else {
887  }
888 }
889 
890 /**
891  * Sets the special value that means "no data" when it appears in the pfm
892  * file.
893  */
894 void PfmFile::
895 set_no_data_value(const LPoint4f &no_data_value) {
896  nassertv(is_valid());
897 
898  _has_no_data_value = true;
899  _has_no_data_threshold = false;
900  _no_data_value = no_data_value;
901  switch (_num_channels) {
902  case 1:
903  _has_point = has_point_1;
904  break;
905  case 2:
906  _has_point = has_point_2;
907  break;
908  case 3:
909  _has_point = has_point_3;
910  break;
911  case 4:
912  _has_point = has_point_4;
913  break;
914  default:
915  nassert_raise("unexpected channel count");
916  break;
917  }
918 }
919 
920 /**
921  * Sets the special threshold value. Points that are below this value in all
922  * components are considered "no value".
923  */
924 void PfmFile::
925 set_no_data_threshold(const LPoint4f &no_data_value) {
926  nassertv(is_valid());
927 
928  _has_no_data_value = true;
929  _has_no_data_threshold = true;
930  _no_data_value = no_data_value;
931  switch (_num_channels) {
932  case 1:
933  _has_point = has_point_threshold_1;
934  break;
935  case 2:
936  _has_point = has_point_threshold_2;
937  break;
938  case 3:
939  _has_point = has_point_threshold_3;
940  break;
941  case 4:
942  _has_point = has_point_threshold_4;
943  break;
944  default:
945  nassert_raise("unexpected channel count");
946  break;
947  }
948 }
949 
950 /**
951  * Applies a simple filter to resample the pfm file in-place to the indicated
952  * size. Don't confuse this with applying a scale to all of the points via
953  * xform().
954  */
955 void PfmFile::
956 resize(int new_x_size, int new_y_size) {
957  if (_x_size == 0 || _y_size == 0 || new_x_size == 0 || new_y_size == 0) {
958  clear(new_x_size, new_y_size, _num_channels);
959  return;
960  }
961 
962  if (new_x_size == _x_size && new_y_size == _y_size) {
963  return;
964  }
965 
966  PfmFile result;
967  result.clear(new_x_size, new_y_size, _num_channels);
968  if (_has_no_data_value) {
969  result.fill(_no_data_value);
970  }
971 
972  if (pfm_resize_quick && new_x_size <= _x_size && new_y_size <= _y_size) {
973  // If we're downscaling, we can use quick_filter, which is faster.
974  result.quick_filter_from(*this);
975 
976  } else {
977  // Otherwise, we should use box_filter() or gaussian_filter, which are
978  // more general.
979  if (pfm_resize_gaussian) {
980  result.gaussian_filter_from(pfm_resize_radius, *this);
981  } else {
982  result.box_filter_from(pfm_resize_radius, *this);
983  }
984  }
985 
986  _table.swap(result._table);
987  _x_size = new_x_size;
988  _y_size = new_y_size;
989 }
990 
991 /**
992  * Resizes from the given image, with a fixed radius of 0.5. This is a very
993  * specialized and simple algorithm that doesn't handle dropping below the
994  * Nyquist rate very well, but is quite a bit faster than the more general
995  * box_filter(), above.
996  */
997 void PfmFile::
999  if (_x_size == 0 || _y_size == 0) {
1000  return;
1001  }
1002 
1003  Table new_data;
1004  new_data.reserve(_table.size());
1005 
1006  PN_float32 from_x0, from_x1, from_y0, from_y1;
1007 
1008  int orig_x_size = from.get_x_size();
1009  int orig_y_size = from.get_y_size();
1010 
1011  PN_float32 x_scale = 1.0;
1012  PN_float32 y_scale = 1.0;
1013 
1014  if (_x_size > 1) {
1015  x_scale = (PN_float32)orig_x_size / (PN_float32)_x_size;
1016  }
1017  if (_y_size > 1) {
1018  y_scale = (PN_float32)orig_y_size / (PN_float32)_y_size;
1019  }
1020 
1021  switch (_num_channels) {
1022  case 1:
1023  {
1024  from_y0 = 0.0;
1025  for (int to_y = 0; to_y < _y_size; ++to_y) {
1026  from_y1 = (to_y + 1.0) * y_scale;
1027  from_y1 = min(from_y1, (PN_float32)orig_y_size);
1028 
1029  from_x0 = 0.0;
1030  for (int to_x = 0; to_x < _x_size; ++to_x) {
1031  from_x1 = (to_x + 1.0) * x_scale;
1032  from_x1 = min(from_x1, (PN_float32)orig_x_size);
1033 
1034  // Now the box from (from_x0, from_y0) - (from_x1, from_y1) but not
1035  // including (from_x1, from_y1) maps to the pixel (to_x, to_y).
1036  PN_float32 result;
1037  from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1);
1038  new_data.push_back(result);
1039 
1040  from_x0 = from_x1;
1041  }
1042  from_y0 = from_y1;
1043  }
1044  }
1045  break;
1046 
1047  case 2:
1048  {
1049  from_y0 = 0.0;
1050  for (int to_y = 0; to_y < _y_size; ++to_y) {
1051  from_y1 = (to_y + 1.0) * y_scale;
1052  from_y1 = min(from_y1, (PN_float32)orig_y_size);
1053 
1054  from_x0 = 0.0;
1055  for (int to_x = 0; to_x < _x_size; ++to_x) {
1056  from_x1 = (to_x + 1.0) * x_scale;
1057  from_x1 = min(from_x1, (PN_float32)orig_x_size);
1058 
1059  // Now the box from (from_x0, from_y0) - (from_x1, from_y1) but not
1060  // including (from_x1, from_y1) maps to the pixel (to_x, to_y).
1061  LPoint2f result;
1062  from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1);
1063  new_data.push_back(result[0]);
1064  new_data.push_back(result[1]);
1065 
1066  from_x0 = from_x1;
1067  }
1068  from_y0 = from_y1;
1069  }
1070  }
1071  break;
1072 
1073  case 3:
1074  {
1075  from_y0 = 0.0;
1076  for (int to_y = 0; to_y < _y_size; ++to_y) {
1077  from_y1 = (to_y + 1.0) * y_scale;
1078  from_y1 = min(from_y1, (PN_float32)orig_y_size);
1079 
1080  from_x0 = 0.0;
1081  for (int to_x = 0; to_x < _x_size; ++to_x) {
1082  from_x1 = (to_x + 1.0) * x_scale;
1083  from_x1 = min(from_x1, (PN_float32)orig_x_size);
1084 
1085  // Now the box from (from_x0, from_y0) - (from_x1, from_y1) but not
1086  // including (from_x1, from_y1) maps to the pixel (to_x, to_y).
1087  LPoint3f result;
1088  from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1);
1089  new_data.push_back(result[0]);
1090  new_data.push_back(result[1]);
1091  new_data.push_back(result[2]);
1092 
1093  from_x0 = from_x1;
1094  }
1095  from_y0 = from_y1;
1096  }
1097  }
1098  break;
1099 
1100  case 4:
1101  {
1102  from_y0 = 0.0;
1103  for (int to_y = 0; to_y < _y_size; ++to_y) {
1104  from_y1 = (to_y + 1.0) * y_scale;
1105  from_y1 = min(from_y1, (PN_float32)orig_y_size);
1106 
1107  from_x0 = 0.0;
1108  for (int to_x = 0; to_x < _x_size; ++to_x) {
1109  from_x1 = (to_x + 1.0) * x_scale;
1110  from_x1 = min(from_x1, (PN_float32)orig_x_size);
1111 
1112  // Now the box from (from_x0, from_y0) - (from_x1, from_y1) but not
1113  // including (from_x1, from_y1) maps to the pixel (to_x, to_y).
1114  LPoint4f result;
1115  from.box_filter_region(result, from_x0, from_y0, from_x1, from_y1);
1116  new_data.push_back(result[0]);
1117  new_data.push_back(result[1]);
1118  new_data.push_back(result[2]);
1119  new_data.push_back(result[3]);
1120 
1121  from_x0 = from_x1;
1122  }
1123  from_y0 = from_y1;
1124  }
1125  }
1126  break;
1127 
1128  default:
1129  nassert_raise("unexpected channel count");
1130  return;
1131  }
1132 
1133  new_data.push_back(0.0);
1134  new_data.push_back(0.0);
1135  new_data.push_back(0.0);
1136  new_data.push_back(0.0);
1137 
1138  nassertv(new_data.size() == _table.size());
1139  _table.swap(new_data);
1140 }
1141 
1142 /**
1143  * Performs an in-place reversal of the row (y) data.
1144  */
1145 void PfmFile::
1147  nassertv(is_valid());
1148 
1149  Table reversed;
1150  reversed.reserve(_table.size());
1151  int row_size = _x_size * _num_channels;
1152  for (int yi = 0; yi < _y_size; ++yi) {
1153  int source_yi = _y_size - 1 - yi;
1154  int start = source_yi * row_size;
1155  reversed.insert(reversed.end(),
1156  _table.begin() + start, _table.begin() + start + row_size);
1157  }
1158 
1159  nassertv(reversed.size() <= _table.size());
1160  // Also add in the extra buffer at the end.
1161  reversed.insert(reversed.end(), _table.size() - reversed.size(), (PN_float32)0.0);
1162 
1163  _table.swap(reversed);
1164 }
1165 
1166 /**
1167  * Reverses, transposes, and/or rotates the table in-place according to the
1168  * specified parameters. If flip_x is true, the x axis is reversed; if flip_y
1169  * is true, the y axis is reversed. Then, if transpose is true, the x and y
1170  * axes are exchanged. These parameters can be used to select any combination
1171  * of 90-degree or 180-degree rotations and flips.
1172  */
1173 void PfmFile::
1174 flip(bool flip_x, bool flip_y, bool transpose) {
1175  nassertv(is_valid());
1176 
1177  Table flipped;
1178  flipped.reserve(_table.size());
1179 
1180  if (transpose) {
1181  // Transposed case. X becomes Y, Y becomes X.
1182  for (int xi = 0; xi < _x_size; ++xi) {
1183  int source_xi = !flip_x ? xi : _x_size - 1 - xi;
1184  for (int yi = 0; yi < _y_size; ++yi) {
1185  int source_yi = !flip_y ? yi : _y_size - 1 - yi;
1186  const PN_float32 *p = &_table[(source_yi * _x_size + source_xi) * _num_channels];
1187  for (int ci = 0; ci < _num_channels; ++ci) {
1188  flipped.push_back(p[ci]);
1189  }
1190  }
1191  }
1192 
1193  int t = _x_size;
1194  _x_size = _y_size;
1195  _y_size = t;
1196 
1197  } else {
1198  // Non-transposed. X is X, Y is Y.
1199  for (int yi = 0; yi < _y_size; ++yi) {
1200  int source_yi = !flip_y ? yi : _y_size - 1 - yi;
1201  for (int xi = 0; xi < _x_size; ++xi) {
1202  int source_xi = !flip_x ? xi : _x_size - 1 - xi;
1203  const PN_float32 *p = &_table[(source_yi * _x_size + source_xi) * _num_channels];
1204  for (int ci = 0; ci < _num_channels; ++ci) {
1205  flipped.push_back(p[ci]);
1206  }
1207  }
1208  }
1209  }
1210 
1211  nassertv(flipped.size() <= _table.size());
1212  // Also add in the extra buffer at the end.
1213  flipped.insert(flipped.end(), _table.size() - flipped.size(), (PN_float32)0.0);
1214 
1215  _table.swap(flipped);
1216 }
1217 
1218 /**
1219  * Applies the indicated transform matrix to all points in-place.
1220  */
1221 void PfmFile::
1222 xform(const LMatrix4f &transform) {
1223  nassertv(is_valid());
1224 
1225  int num_channels = get_num_channels();
1226  switch (num_channels) {
1227  case 1:
1228  {
1229  for (int yi = 0; yi < _y_size; ++yi) {
1230  for (int xi = 0; xi < _x_size; ++xi) {
1231  if (!has_point(xi, yi)) {
1232  continue;
1233  }
1234  PN_float32 pi = get_point1(xi, yi);
1235  LPoint3f po = transform.xform_point(LPoint3f(pi, 0.0, 0.0));
1236  set_point1(xi, yi, po[0]);
1237  }
1238  }
1239  }
1240  break;
1241 
1242  case 2:
1243  {
1244  for (int yi = 0; yi < _y_size; ++yi) {
1245  for (int xi = 0; xi < _x_size; ++xi) {
1246  if (!has_point(xi, yi)) {
1247  continue;
1248  }
1249  LPoint2f pi = get_point2(xi, yi);
1250  LPoint3f po = transform.xform_point(LPoint3f(pi[0], pi[1], 0.0));
1251  set_point2(xi, yi, LPoint2f(po[0], po[1]));
1252  }
1253  }
1254  }
1255  break;
1256 
1257  case 3:
1258  {
1259  for (int yi = 0; yi < _y_size; ++yi) {
1260  for (int xi = 0; xi < _x_size; ++xi) {
1261  if (!has_point(xi, yi)) {
1262  continue;
1263  }
1264  LPoint3f &p = modify_point3(xi, yi);
1265  transform.xform_point_general_in_place(p);
1266  }
1267  }
1268  }
1269  break;
1270 
1271  case 4:
1272  {
1273  for (int yi = 0; yi < _y_size; ++yi) {
1274  for (int xi = 0; xi < _x_size; ++xi) {
1275  if (!has_point(xi, yi)) {
1276  continue;
1277  }
1278  LPoint4f &p = modify_point4(xi, yi);
1279  transform.xform_in_place(p);
1280  }
1281  }
1282  }
1283  break;
1284  }
1285 }
1286 
1287 /**
1288  * Applies the distortion indicated in the supplied dist map to the current
1289  * map. The dist map is understood to be a mapping of points in the range
1290  * 0..1 in the first two dimensions.
1291  *
1292  * The operation can be expressed symbolically as:
1293  *
1294  * this(u, v) = this(dist(u, v))
1295  *
1296  * If scale_factor is not 1, it should be a value > 1, and it specifies the
1297  * factor to upscale the working table while processing, to reduce artifacts
1298  * from integer truncation.
1299  *
1300  * By convention, the y axis is inverted in the distortion map relative to the
1301  * coordinates here. A y value of 0 in the distortion map corresponds with a
1302  * v value of 1 in this file.
1303  */
1304 void PfmFile::
1305 forward_distort(const PfmFile &dist, PN_float32 scale_factor) {
1306  int working_x_size = (int)cceil(_x_size * scale_factor);
1307  int working_y_size = (int)cceil(_y_size * scale_factor);
1308 
1309  working_x_size = max(working_x_size, dist.get_x_size());
1310  working_y_size = max(working_y_size, dist.get_y_size());
1311 
1312  const PfmFile *source_p = this;
1313  PfmFile scaled_source;
1314  if ((*this).get_x_size() != working_x_size || (*this).get_y_size() != working_y_size) {
1315  // Rescale the source file as needed.
1316  scaled_source = (*this);
1317  scaled_source.resize(working_x_size, working_y_size);
1318  source_p = &scaled_source;
1319  }
1320 
1321  const PfmFile *dist_p = &dist;
1322  PfmFile scaled_dist;
1323  if (dist.get_x_size() != working_x_size || dist.get_y_size() != working_y_size) {
1324  // Rescale the dist file as needed.
1325  scaled_dist = dist;
1326  scaled_dist.resize(working_x_size, working_y_size);
1327  dist_p = &scaled_dist;
1328  }
1329 
1330  PfmFile result;
1331  result.clear(working_x_size, working_y_size, _num_channels);
1332 
1333  if (_has_no_data_value) {
1334  result.set_no_data_value(_no_data_value);
1335  result.fill(_no_data_value);
1336  }
1337 
1338  for (int yi = 0; yi < working_y_size; ++yi) {
1339  for (int xi = 0; xi < working_x_size; ++xi) {
1340  if (!dist_p->has_point(xi, yi)) {
1341  continue;
1342  }
1343  LPoint2f uv = dist_p->get_point2(xi, yi);
1344  LPoint3f p;
1345  if (!source_p->calc_bilinear_point(p, uv[0], 1.0 - uv[1])) {
1346  continue;
1347  }
1348  nassertv(!p.is_nan());
1349  result.set_point(xi, working_y_size - 1 - yi, p);
1350  }
1351  }
1352 
1353  // Resize to the target size for completion.
1354  result.resize(_x_size, _y_size);
1355 
1356  nassertv(result._table.size() == _table.size());
1357  _table.swap(result._table);
1358 }
1359 
1360 /**
1361  * Applies the distortion indicated in the supplied dist map to the current
1362  * map. The dist map is understood to be a mapping of points in the range
1363  * 0..1 in the first two dimensions.
1364  *
1365  * The operation can be expressed symbolically as:
1366  *
1367  * this(u, v) = dist(this(u, v))
1368  *
1369  * If scale_factor is not 1, it should be a value > 1, and it specifies the
1370  * factor to upscale the working table while processing, to reduce artifacts
1371  * from integer truncation.
1372  *
1373  * By convention, the y axis in inverted in the distortion map relative to the
1374  * coordinates here. A y value of 0 in the distortion map corresponds with a
1375  * v value of 1 in this file.
1376  */
1377 void PfmFile::
1378 reverse_distort(const PfmFile &dist, PN_float32 scale_factor) {
1379  int working_x_size = (int)cceil(_x_size * scale_factor);
1380  int working_y_size = (int)cceil(_y_size * scale_factor);
1381 
1382  working_x_size = max(working_x_size, dist.get_x_size());
1383  working_y_size = max(working_y_size, dist.get_y_size());
1384 
1385  const PfmFile *source_p = this;
1386  PfmFile scaled_source;
1387  if ((*this).get_x_size() != working_x_size || (*this).get_y_size() != working_y_size) {
1388  // Rescale the source file as needed.
1389  scaled_source = (*this);
1390  scaled_source.resize(working_x_size, working_y_size);
1391  source_p = &scaled_source;
1392  }
1393 
1394  const PfmFile *dist_p = &dist;
1395  PfmFile scaled_dist;
1396  if (dist.get_x_size() != working_x_size || dist.get_y_size() != working_y_size) {
1397  // Rescale the dist file as needed.
1398  scaled_dist = dist;
1399  scaled_dist.resize(working_x_size, working_y_size);
1400  dist_p = &scaled_dist;
1401  }
1402 
1403  PfmFile result;
1404  result.clear(working_x_size, working_y_size, _num_channels);
1405 
1406  if (_has_no_data_value) {
1407  result.set_no_data_value(_no_data_value);
1408  result.fill(_no_data_value);
1409  }
1410 
1411  for (int yi = 0; yi < working_y_size; ++yi) {
1412  for (int xi = 0; xi < working_x_size; ++xi) {
1413  if (!source_p->has_point(xi, yi)) {
1414  continue;
1415  }
1416  LPoint2f uv = source_p->get_point2(xi, yi);
1417  LPoint3f p;
1418  if (!dist_p->calc_bilinear_point(p, uv[0], 1.0 - uv[1])) {
1419  continue;
1420  }
1421  result.set_point(xi, yi, LPoint3f(p[0], 1.0 - p[1], p[2]));
1422  }
1423  }
1424 
1425  // Resize to the target size for completion.
1426  result.resize(_x_size, _y_size);
1427 
1428  nassertv(result._table.size() == _table.size());
1429  _table.swap(result._table);
1430 }
1431 
1432 /**
1433  * Assumes that lut is an X by 1, 1-component PfmFile whose X axis maps points
1434  * to target points. For each point in this pfm file, computes: p(u,
1435  * v)[channel] = lut(p(u, v)[channel] * x_scale, 0)[0]
1436  */
1437 void PfmFile::
1438 apply_1d_lut(int channel, const PfmFile &lut, PN_float32 x_scale) {
1439  for (int yi = 0; yi < _y_size; ++yi) {
1440  for (int xi = 0; xi < _x_size; ++xi) {
1441  if (!has_point(xi, yi)) {
1442  continue;
1443  }
1444 
1445  PN_float32 v = get_channel(xi, yi, channel);
1446  LPoint3f p;
1447  if (!lut.calc_bilinear_point(p, v * x_scale, 0.5)) {
1448  continue;
1449  }
1450  set_channel(xi, yi, channel, p[0]);
1451  }
1452  }
1453 }
1454 
1455 /**
1456  * Wherever there is missing data in this PfmFile (that is, wherever
1457  * has_point() returns false), copy data from the other PfmFile, which must be
1458  * exactly the same dimensions as this one.
1459  */
1460 void PfmFile::
1461 merge(const PfmFile &other) {
1462  nassertv(is_valid() && other.is_valid());
1463  nassertv(other._x_size == _x_size && other._y_size == _y_size && other._num_channels == _num_channels);
1464 
1465  if (!_has_no_data_value) {
1466  // Trivial no-op.
1467  return;
1468  }
1469 
1470  size_t point_size = _num_channels * sizeof(PN_float32);
1471  for (int y = 0; y < _y_size; ++y) {
1472  for (int x = 0; x < _x_size; ++x) {
1473  if (!has_point(x, y) && other.has_point(x, y)) {
1474  memcpy(&_table[(y * _x_size + x) * _num_channels],
1475  &other._table[(y * _x_size + x) * _num_channels],
1476  point_size);
1477  }
1478  }
1479  }
1480 }
1481 
1482 /**
1483  * Wherever there is missing data in the other PfmFile, set this the
1484  * corresponding point in this PfmFile to missing as well, so that this
1485  * PfmFile has only points where both files have points.
1486  *
1487  * The point is set to "missing" by setting it the no_data_value.
1488  */
1489 void PfmFile::
1490 apply_mask(const PfmFile &other) {
1491  nassertv(is_valid() && other.is_valid());
1492  nassertv(other._x_size == _x_size && other._y_size == _y_size);
1493 
1494  if (!other._has_no_data_value || !_has_no_data_value) {
1495  // Trivial no-op.
1496  return;
1497  }
1498 
1499  for (int yi = 0; yi < _y_size; ++yi) {
1500  for (int xi = 0; xi < _x_size; ++xi) {
1501  if (!other.has_point(xi, yi)) {
1502  set_point4(xi, yi, _no_data_value);
1503  }
1504  }
1505  }
1506 }
1507 
1508 /**
1509  * Copies just the specified channel values from the indicated PfmFile (which
1510  * could be same as this PfmFile) into the specified channel of this one.
1511  */
1512 void PfmFile::
1513 copy_channel(int to_channel, const PfmFile &other, int from_channel) {
1514  nassertv(is_valid() && other.is_valid());
1515  nassertv(other._x_size == _x_size && other._y_size == _y_size);
1516  nassertv(to_channel >= 0 && to_channel < get_num_channels() &&
1517  from_channel >= 0 && from_channel < other.get_num_channels());
1518 
1519  for (int yi = 0; yi < _y_size; ++yi) {
1520  for (int xi = 0; xi < _x_size; ++xi) {
1521  set_channel(xi, yi, to_channel, other.get_channel(xi, yi, from_channel));
1522  }
1523  }
1524 }
1525 
1526 /**
1527  * Copies just the specified channel values from the indicated PfmFile, but
1528  * only where the other file has a data point.
1529  */
1530 void PfmFile::
1531 copy_channel_masked(int to_channel, const PfmFile &other, int from_channel) {
1532  nassertv(is_valid() && other.is_valid());
1533  nassertv(other._x_size == _x_size && other._y_size == _y_size);
1534  nassertv(to_channel >= 0 && to_channel < get_num_channels() &&
1535  from_channel >= 0 && from_channel < other.get_num_channels());
1536 
1537  for (int yi = 0; yi < _y_size; ++yi) {
1538  for (int xi = 0; xi < _x_size; ++xi) {
1539  if (other.has_point(xi, yi)) {
1540  set_channel(xi, yi, to_channel, other.get_channel(xi, yi, from_channel));
1541  }
1542  }
1543  }
1544 }
1545 
1546 /**
1547  * Reduces the PFM file to the cells in the rectangle bounded by (x_begin,
1548  * x_end, y_begin, y_end), where the _end cells are not included.
1549  */
1550 void PfmFile::
1551 apply_crop(int x_begin, int x_end, int y_begin, int y_end) {
1552  nassertv(x_begin >= 0 && x_begin <= x_end && x_end <= _x_size);
1553  nassertv(y_begin >= 0 && y_begin <= y_end && y_end <= _y_size);
1554 
1555  int new_x_size = x_end - x_begin;
1556  int new_y_size = y_end - y_begin;
1557  Table new_table;
1558  size_t new_size = (size_t)new_x_size * (size_t)new_y_size * (size_t)_num_channels;
1559 
1560  // We allocate a little bit bigger to allow safe overflow: you can call
1561  // get_point3() or get_point4() on the last point of a 1- or 3-channel
1562  // image.
1563  new_table.insert(new_table.end(), new_size + 4, (PN_float32)0.0);
1564 
1565  for (int yi = 0; yi < new_y_size; ++yi) {
1566  memcpy(&new_table[(yi * new_x_size) * _num_channels],
1567  &_table[((yi + y_begin) * _x_size + x_begin) * _num_channels],
1568  new_x_size * sizeof(PN_float32) * _num_channels);
1569  }
1570 
1571  nassertv(new_table.size() == new_size + 4);
1572  _table.swap(new_table);
1573  _x_size = new_x_size;
1574  _y_size = new_y_size;
1575 }
1576 
1577 /**
1578  * Replaces this PfmFile with a new PfmFile of size x_size x y_size x 3,
1579  * containing the x y 0 values in the range 0 .. 1 according to the x y index.
1580  */
1581 void PfmFile::
1582 clear_to_texcoords(int x_size, int y_size) {
1583  clear(x_size, y_size, 3);
1584 
1585  LPoint2f uv_scale(1.0, 1.0);
1586  if (_x_size > 0) {
1587  uv_scale[0] = 1.0f / PN_float32(_x_size);
1588  }
1589  if (_y_size > 0) {
1590  uv_scale[1] = 1.0f / PN_float32(_y_size);
1591  }
1592 
1593  for (int yi = 0; yi < _y_size; ++yi) {
1594  for (int xi = 0; xi < _x_size; ++xi) {
1595  LPoint3f uv((PN_float32(xi) + 0.5) * uv_scale[0],
1596  (PN_float32(yi) + 0.5) * uv_scale[1],
1597  0.0f);
1598  set_point(xi, yi, uv);
1599  }
1600  }
1601 }
1602 
1603 /**
1604  * Applies delta * t to the point values within radius (xr, yr) distance of
1605  * (xc, yc). The t value is scaled from 1.0 at the center to 0.0 at radius
1606  * (xr, yr), and this scale follows the specified exponent. Returns the
1607  * number of points affected.
1608  */
1609 int PfmFile::
1610 pull_spot(const LPoint4f &delta, float xc, float yc,
1611  float xr, float yr, float exponent) {
1612  int minx = max((int)cceil(xc - xr), 0);
1613  int maxx = min((int)cfloor(xc + xr), _x_size - 1);
1614  int miny = max((int)cceil(yc - yr), 0);
1615  int maxy = min((int)cfloor(yc + yr), _y_size - 1);
1616 
1617  int count = 0;
1618  for (int yi = miny; yi <= maxy; ++yi) {
1619  for (int xi = minx; xi <= maxx; ++xi) {
1620  float xd = ((float)xi - xc) / xr;
1621  float yd = ((float)yi - yc) / yr;
1622  float r2 = xd * xd + yd * yd;
1623  if (r2 >= 1.0f) {
1624  continue;
1625  }
1626  PN_float32 t = (PN_float32)cpow(1.0f - csqrt(r2), exponent);
1627 
1628  PN_float32 *f = &_table[(yi * _x_size + xi) * _num_channels];
1629  for (int ci = 0; ci < _num_channels; ++ci) {
1630  f[ci] += delta[ci] * t;
1631  }
1632  ++count;
1633  }
1634  }
1635 
1636  return count;
1637 }
1638 
1639 /**
1640  * Calculates the minimum and maximum vertices of all points within the table.
1641  * Assumes the table contains 3-D points.
1642  *
1643  * The return value is true if any points in the table, or false if none are.
1644  */
1645 bool PfmFile::
1646 calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point) const {
1647  min_point.set(0.0f, 0.0f, 0.0f);
1648  max_point.set(0.0f, 0.0f, 0.0f);
1649 
1650  bool found_any = false;
1651  for (int yi = 0; yi < _y_size; ++yi) {
1652  for (int xi = 0; xi < _x_size; ++xi) {
1653  if (!has_point(xi, yi)) {
1654  continue;
1655  }
1656 
1657  const LPoint3f &point = get_point(xi, yi);
1658  if (!found_any) {
1659  min_point = point;
1660  max_point = point;
1661  found_any = true;
1662  } else {
1663  min_point.set(min(min_point[0], point[0]),
1664  min(min_point[1], point[1]),
1665  min(min_point[2], point[2]));
1666  max_point.set(max(max_point[0], point[0]),
1667  max(max_point[1], point[1]),
1668  max(max_point[2], point[2]));
1669  }
1670  }
1671  }
1672 
1673  return found_any;
1674 }
1675 
1676 /**
1677  * Computes the minmax bounding volume of the points in 3-D space, assuming
1678  * the points represent a mostly-planar surface.
1679  *
1680  * This algorithm works by sampling the (square) sample_radius pixels at the
1681  * four point_dist corners around the center (cx - pd, cx + pd) and so on, to
1682  * approximate the plane of the surface. Then all of the points are projected
1683  * into that plane and the bounding volume of the entire mesh within that
1684  * plane is determined. If points_only is true, the bounding volume of only
1685  * those four points is determined.
1686  *
1687  * center, point_dist and sample_radius are in UV space, i.e. in the range
1688  * 0..1.
1689  */
1691 compute_planar_bounds(const LPoint2f &center, PN_float32 point_dist, PN_float32 sample_radius, bool points_only) const {
1692  LPoint3f p0, p1, p2, p3;
1693  compute_sample_point(p0, center[0] + point_dist, center[1] - point_dist, sample_radius);
1694  compute_sample_point(p1, center[0] + point_dist, center[1] + point_dist, sample_radius);
1695  compute_sample_point(p2, center[0] - point_dist, center[1] + point_dist, sample_radius);
1696  compute_sample_point(p3, center[0] - point_dist, center[1] - point_dist, sample_radius);
1697 
1698  LPoint3f normal;
1699 
1700  normal[0] = p0[1] * p1[2] - p0[2] * p1[1];
1701  normal[1] = p0[2] * p1[0] - p0[0] * p1[2];
1702  normal[2] = p0[0] * p1[1] - p0[1] * p1[0];
1703 
1704  normal[0] += p1[1] * p2[2] - p1[2] * p2[1];
1705  normal[1] += p1[2] * p2[0] - p1[0] * p2[2];
1706  normal[2] += p1[0] * p2[1] - p1[1] * p2[0];
1707 
1708  normal[0] += p2[1] * p3[2] - p2[2] * p3[1];
1709  normal[1] += p2[2] * p3[0] - p2[0] * p3[2];
1710  normal[2] += p2[0] * p3[1] - p2[1] * p3[0];
1711 
1712  normal[0] += p3[1] * p0[2] - p3[2] * p0[1];
1713  normal[1] += p3[2] * p0[0] - p3[0] * p0[2];
1714  normal[2] += p3[0] * p0[1] - p3[1] * p0[0];
1715 
1716  normal.normalize();
1717 
1718  LVector3f up = (p1 - p0) + (p2 - p3);
1719  LPoint3f pcenter = ((p0 + p1 + p2 + p3) * 0.25);
1720 
1721  // Compute the transform necessary to rotate all of the points into the Y =
1722  // 0 plane.
1723  LMatrix4f rotate;
1724  look_at(rotate, normal, up);
1725 
1726  LMatrix4f rinv;
1727  rinv.invert_from(rotate);
1728 
1729  LPoint3f trans = pcenter * rinv;
1730  rinv.set_row(3, -trans);
1731  rotate.invert_from(rinv);
1732 
1733  // Now determine the minmax.
1734  PN_float32 min_x, min_y, min_z, max_x, max_y, max_z;
1735  if (points_only) {
1736  const LPoint3f points[4] = {
1737  p0 * rinv,
1738  p1 * rinv,
1739  p2 * rinv,
1740  p3 * rinv,
1741  };
1742  const LPoint3f &point = points[0];
1743  min_x = point[0];
1744  min_y = point[1];
1745  min_z = point[2];
1746  max_x = point[0];
1747  max_y = point[1];
1748  max_z = point[2];
1749 
1750  for (int i = 1; i < 4; ++i) {
1751  const LPoint3f &point = points[i];
1752  min_x = min(min_x, point[0]);
1753  min_y = min(min_y, point[1]);
1754  min_z = min(min_z, point[2]);
1755  max_x = max(max_x, point[0]);
1756  max_y = max(max_y, point[1]);
1757  max_z = max(max_z, point[2]);
1758  }
1759  } else {
1760  bool got_point = false;
1761  for (int yi = 0; yi < _y_size; ++yi) {
1762  for (int xi = 0; xi < _x_size; ++xi) {
1763  if (!has_point(xi, yi)) {
1764  continue;
1765  }
1766 
1767  LPoint3f point = get_point(xi, yi) * rinv;
1768  if (!got_point) {
1769  min_x = point[0];
1770  min_y = point[1];
1771  min_z = point[2];
1772  max_x = point[0];
1773  max_y = point[1];
1774  max_z = point[2];
1775  got_point = true;
1776  } else {
1777  min_x = min(min_x, point[0]);
1778  min_y = min(min_y, point[1]);
1779  min_z = min(min_z, point[2]);
1780  max_x = max(max_x, point[0]);
1781  max_y = max(max_y, point[1]);
1782  max_z = max(max_z, point[2]);
1783  }
1784  }
1785  }
1786  if (!got_point) {
1787  min_x = 0.0f;
1788  min_y = 0.0f;
1789  min_z = 0.0f;
1790  max_x = 0.0f;
1791  max_y = 0.0f;
1792  max_z = 0.0f;
1793  }
1794  }
1795 
1796  PT(BoundingHexahedron) bounds;
1797 
1798  // We create a BoundingHexahedron with the points in a particular well-
1799  // defined order, based on the current coordinate system.
1800  CoordinateSystem cs = get_default_coordinate_system();
1801  switch (cs) {
1802  case CS_yup_right:
1803  bounds = new BoundingHexahedron
1804  (LPoint3(min_x, min_y, min_z), LPoint3(max_x, min_y, min_z),
1805  LPoint3(min_x, max_y, min_z), LPoint3(max_x, max_y, min_z),
1806  LPoint3(min_x, min_y, max_z), LPoint3(max_x, min_y, max_z),
1807  LPoint3(min_x, max_y, max_z), LPoint3(max_x, max_y, max_z));
1808  break;
1809 
1810  case CS_zup_right:
1811  bounds = new BoundingHexahedron
1812  (LPoint3(min_x, min_y, min_z), LPoint3(max_x, min_y, min_z),
1813  LPoint3(min_x, min_y, max_z), LPoint3(max_x, min_y, max_z),
1814  LPoint3(min_x, max_y, min_z), LPoint3(max_x, max_y, min_z),
1815  LPoint3(min_x, max_y, max_z), LPoint3(max_x, max_y, max_z));
1816  break;
1817 
1818  case CS_yup_left:
1819  bounds = new BoundingHexahedron
1820  (LPoint3(max_x, min_y, max_z), LPoint3(min_x, min_y, max_z),
1821  LPoint3(max_x, max_y, max_z), LPoint3(min_x, max_y, max_z),
1822  LPoint3(max_x, min_y, min_z), LPoint3(min_x, min_y, min_z),
1823  LPoint3(max_x, max_y, min_z), LPoint3(min_x, max_y, min_z));
1824  break;
1825 
1826  case CS_zup_left:
1827  bounds = new BoundingHexahedron
1828  (LPoint3(max_x, max_y, min_z), LPoint3(min_x, max_y, min_z),
1829  LPoint3(max_x, max_y, max_z), LPoint3(min_x, max_y, max_z),
1830  LPoint3(max_x, min_y, min_z), LPoint3(min_x, min_y, min_z),
1831  LPoint3(max_x, min_y, max_z), LPoint3(min_x, min_y, max_z));
1832  break;
1833 
1834  default:
1835  nassert_raise("invalid coordinate system");
1836  return nullptr;
1837  }
1838 
1839  // Rotate the bounding volume back into the original space of the screen.
1840  bounds->xform(LCAST(PN_stdfloat, rotate));
1841 
1842  return bounds;
1843 }
1844 
1845 /**
1846  * Computes the average of all the point within sample_radius (manhattan
1847  * distance) and the indicated point.
1848  *
1849  * The point coordinates are given in UV space, in the range 0..1.
1850  */
1851 void PfmFile::
1852 compute_sample_point(LPoint3f &result,
1853  PN_float32 x, PN_float32 y, PN_float32 sample_radius) const {
1854  x *= _x_size;
1855  y *= _y_size;
1856  PN_float32 xr = sample_radius * _x_size;
1857  PN_float32 yr = sample_radius * _y_size;
1858 
1859  switch (_num_channels) {
1860  case 1:
1861  {
1862  PN_float32 result1;
1863  box_filter_region(result1, x - xr, y - yr, x + xr, y + yr);
1864  result.set(result1, 0.0, 0.0);
1865  }
1866  break;
1867 
1868  case 2:
1869  {
1870  LPoint2f result2;
1871  box_filter_region(result2, x - xr, y - yr, x + xr, y + yr);
1872  result.set(result2[0], result2[1], 0.0);
1873  }
1874  break;
1875 
1876  case 3:
1877  box_filter_region(result, x - xr, y - yr, x + xr, y + yr);
1878  break;
1879 
1880  case 4:
1881  {
1882  LPoint4f result4;
1883  box_filter_region(result4, x - xr, y - yr, x + xr, y + yr);
1884  result.set(result4[0], result4[1], result4[2]);
1885  }
1886  break;
1887 
1888  default:
1889  nassert_raise("unexpected channel count");
1890  break;
1891  }
1892 }
1893 
1894 /**
1895  * Copies a rectangular area of another image into a rectangular area of this
1896  * image. Both images must already have been initialized. The upper-left
1897  * corner of the region in both images is specified, and the size of the area;
1898  * if the size is omitted, it defaults to the entire other image, or the
1899  * largest piece that will fit.
1900  */
1901 void PfmFile::
1902 copy_sub_image(const PfmFile &copy, int xto, int yto,
1903  int xfrom, int yfrom, int x_size, int y_size) {
1904  int xmin, ymin, xmax, ymax;
1905  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1906  xmin, ymin, xmax, ymax);
1907 
1908  int x, y;
1909  switch (_num_channels) {
1910  case 1:
1911  {
1912  for (y = ymin; y < ymax; y++) {
1913  for (x = xmin; x < xmax; x++) {
1914  if (copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1915  set_point1(x, y, copy.get_point1(x - xmin + xfrom, y - ymin + yfrom));
1916  }
1917  }
1918  }
1919  }
1920  break;
1921 
1922  case 2:
1923  {
1924  for (y = ymin; y < ymax; y++) {
1925  for (x = xmin; x < xmax; x++) {
1926  if (copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1927  set_point2(x, y, copy.get_point2(x - xmin + xfrom, y - ymin + yfrom));
1928  }
1929  }
1930  }
1931  }
1932  break;
1933 
1934  case 3:
1935  {
1936  for (y = ymin; y < ymax; y++) {
1937  for (x = xmin; x < xmax; x++) {
1938  if (copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1939  set_point(x, y, copy.get_point(x - xmin + xfrom, y - ymin + yfrom));
1940  }
1941  }
1942  }
1943  }
1944  break;
1945 
1946  case 4:
1947  {
1948  for (y = ymin; y < ymax; y++) {
1949  for (x = xmin; x < xmax; x++) {
1950  if (copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1951  set_point4(x, y, copy.get_point4(x - xmin + xfrom, y - ymin + yfrom));
1952  }
1953  }
1954  }
1955  }
1956  break;
1957  }
1958 }
1959 
1960 /**
1961  * Behaves like copy_sub_image(), except the copy pixels are added to the
1962  * pixels of the destination, after scaling by the specified pixel_scale.
1963  */
1964 void PfmFile::
1965 add_sub_image(const PfmFile &copy, int xto, int yto,
1966  int xfrom, int yfrom, int x_size, int y_size,
1967  float pixel_scale) {
1968  int xmin, ymin, xmax, ymax;
1969  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1970  xmin, ymin, xmax, ymax);
1971 
1972  int x, y;
1973  switch (_num_channels) {
1974  case 1:
1975  {
1976  for (y = ymin; y < ymax; y++) {
1977  for (x = xmin; x < xmax; x++) {
1978  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1979  set_point1(x, y, get_point1(x, y) + copy.get_point1(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1980  }
1981  }
1982  }
1983  }
1984  break;
1985 
1986  case 2:
1987  {
1988  for (y = ymin; y < ymax; y++) {
1989  for (x = xmin; x < xmax; x++) {
1990  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
1991  set_point2(x, y, get_point2(x, y) + copy.get_point2(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1992  }
1993  }
1994  }
1995  }
1996  break;
1997 
1998  case 3:
1999  {
2000  for (y = ymin; y < ymax; y++) {
2001  for (x = xmin; x < xmax; x++) {
2002  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2003  set_point3(x, y, get_point3(x, y) + copy.get_point3(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
2004  }
2005  }
2006  }
2007  }
2008  break;
2009 
2010  case 4:
2011  {
2012  for (y = ymin; y < ymax; y++) {
2013  for (x = xmin; x < xmax; x++) {
2014  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2015  set_point4(x, y, get_point4(x, y) + copy.get_point4(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
2016  }
2017  }
2018  }
2019  }
2020  break;
2021  }
2022 }
2023 
2024 /**
2025  * Behaves like copy_sub_image(), except the copy pixels are multiplied to the
2026  * pixels of the destination, after scaling by the specified pixel_scale.
2027  */
2028 void PfmFile::
2029 mult_sub_image(const PfmFile &copy, int xto, int yto,
2030  int xfrom, int yfrom, int x_size, int y_size,
2031  float pixel_scale) {
2032  int xmin, ymin, xmax, ymax;
2033  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
2034  xmin, ymin, xmax, ymax);
2035 
2036  int x, y;
2037  switch (_num_channels) {
2038  case 1:
2039  {
2040  for (y = ymin; y < ymax; y++) {
2041  for (x = xmin; x < xmax; x++) {
2042  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2043  set_point1(x, y, get_point1(x, y) * (copy.get_point1(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale));
2044  }
2045  }
2046  }
2047  }
2048  break;
2049 
2050  case 2:
2051  {
2052  for (y = ymin; y < ymax; y++) {
2053  for (x = xmin; x < xmax; x++) {
2054  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2055  modify_point2(x, y).componentwise_mult(copy.get_point2(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
2056  }
2057  }
2058  }
2059  }
2060  break;
2061 
2062  case 3:
2063  {
2064  for (y = ymin; y < ymax; y++) {
2065  for (x = xmin; x < xmax; x++) {
2066  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2067  modify_point3(x, y).componentwise_mult(copy.get_point3(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
2068  }
2069  }
2070  }
2071  }
2072  break;
2073 
2074  case 4:
2075  {
2076  for (y = ymin; y < ymax; y++) {
2077  for (x = xmin; x < xmax; x++) {
2078  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2079  modify_point4(x, y).componentwise_mult(copy.get_point4(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
2080  }
2081  }
2082  }
2083  }
2084  break;
2085  }
2086 }
2087 
2088 /**
2089  * Behaves like copy_sub_image(), except the copy pixels are divided into the
2090  * pixels of the destination, after scaling by the specified pixel_scale.
2091  * dest(x, y) = dest(x, y) / (copy(x, y) * pixel_scale).
2092  */
2093 void PfmFile::
2094 divide_sub_image(const PfmFile &copy, int xto, int yto,
2095  int xfrom, int yfrom, int x_size, int y_size,
2096  float pixel_scale) {
2097  int xmin, ymin, xmax, ymax;
2098  setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
2099  xmin, ymin, xmax, ymax);
2100 
2101  int x, y;
2102  switch (_num_channels) {
2103  case 1:
2104  {
2105  for (y = ymin; y < ymax; y++) {
2106  for (x = xmin; x < xmax; x++) {
2107  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2108  float val = get_point1(x, y) / copy.get_point1(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale;
2109  if (cnan(val)) {
2110  val = 0.0f;
2111  }
2112  set_point1(x, y, val);
2113  }
2114  }
2115  }
2116  }
2117  break;
2118 
2119  case 2:
2120  {
2121  for (y = ymin; y < ymax; y++) {
2122  for (x = xmin; x < xmax; x++) {
2123  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2124  LPoint2f p = copy.get_point2(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale;
2125  LPoint2f &t = modify_point2(x, y);
2126  t[0] /= p[0];
2127  t[1] /= p[1];
2128  }
2129  }
2130  }
2131  }
2132  break;
2133 
2134  case 3:
2135  {
2136  for (y = ymin; y < ymax; y++) {
2137  for (x = xmin; x < xmax; x++) {
2138  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2139  LPoint3f p = copy.get_point3(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale;
2140  LPoint3f &t = modify_point3(x, y);
2141  t[0] /= p[0];
2142  t[1] /= p[1];
2143  t[2] /= p[2];
2144  }
2145  }
2146  }
2147  }
2148  break;
2149 
2150  case 4:
2151  {
2152  for (y = ymin; y < ymax; y++) {
2153  for (x = xmin; x < xmax; x++) {
2154  if (has_point(x, y) && copy.has_point(x - xmin + xfrom, y - ymin + yfrom)) {
2155  LPoint4f p = copy.get_point4(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale;
2156  LPoint4f &t = modify_point4(x, y);
2157  t[0] /= p[0];
2158  t[1] /= p[1];
2159  t[2] /= p[2];
2160  t[3] /= p[3];
2161  }
2162  }
2163  }
2164  }
2165  break;
2166  }
2167 }
2168 
2169 /**
2170  * Multiplies every point value in the image by a constant floating-point
2171  * multiplier value.
2172  */
2173 void PfmFile::
2174 operator *= (float multiplier) {
2175  nassertv(is_valid());
2176 
2177  switch (_num_channels) {
2178  case 1:
2179  {
2180  for (int y = 0; y < _y_size; ++y) {
2181  for (int x = 0; x < _x_size; ++x) {
2182  if (has_point(x, y)) {
2183  PN_float32 p = get_point1(x, y);
2184  p *= multiplier;
2185  set_point1(x, y, p);
2186  }
2187  }
2188  }
2189  }
2190  break;
2191 
2192  case 2:
2193  {
2194  for (int y = 0; y < _y_size; ++y) {
2195  for (int x = 0; x < _x_size; ++x) {
2196  if (has_point(x, y)) {
2197  LPoint2f &p = modify_point2(x, y);
2198  p *= multiplier;
2199  }
2200  }
2201  }
2202  }
2203  break;
2204 
2205  case 3:
2206  {
2207  for (int y = 0; y < _y_size; ++y) {
2208  for (int x = 0; x < _x_size; ++x) {
2209  if (has_point(x, y)) {
2210  LPoint3f &p = modify_point3(x, y);
2211  p *= multiplier;
2212  }
2213  }
2214  }
2215  }
2216  break;
2217 
2218  case 4:
2219  {
2220  for (int y = 0; y < _y_size; ++y) {
2221  for (int x = 0; x < _x_size; ++x) {
2222  if (has_point(x, y)) {
2223  LPoint4f &p = modify_point4(x, y);
2224  p *= multiplier;
2225  }
2226  }
2227  }
2228  }
2229  break;
2230  }
2231 }
2232 
2233 /**
2234  * index_image is a WxH 1-channel image, while pixel_values is an Nx1
2235  * image with any number of channels. Typically pixel_values will be
2236  * a 256x1 image.
2237  *
2238  * Fills the PfmFile with a new image the same width and height as
2239  * index_image, with the same number of channels as pixel_values.
2240  *
2241  * Each pixel of the new image is computed with the formula:
2242  *
2243  * new_image(x, y) = pixel_values(index_image(x, y)[channel], 0)
2244  *
2245  * At present, no interpolation is performed; the nearest value in
2246  * pixel_values is discovered. This may change in the future.
2247  */
2248 void PfmFile::
2249 indirect_1d_lookup(const PfmFile &index_image, int channel,
2250  const PfmFile &pixel_values) {
2251  clear(index_image.get_x_size(), index_image.get_y_size(),
2252  pixel_values.get_num_channels());
2253 
2254  for (int yi = 0; yi < get_y_size(); ++yi) {
2255  switch (get_num_channels()) {
2256  case 1:
2257  for (int xi = 0; xi < get_x_size(); ++xi) {
2258  int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
2259  nassertv(v >= 0 && v < pixel_values.get_x_size());
2260  set_point1(xi, yi, pixel_values.get_point1(v, 0));
2261  }
2262  break;
2263 
2264  case 2:
2265  for (int xi = 0; xi < get_x_size(); ++xi) {
2266  int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
2267  nassertv(v >= 0 && v < pixel_values.get_x_size());
2268  set_point2(xi, yi, pixel_values.get_point2(v, 0));
2269  }
2270  break;
2271 
2272  case 3:
2273  for (int xi = 0; xi < get_x_size(); ++xi) {
2274  int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
2275  nassertv(v >= 0 && v < pixel_values.get_x_size());
2276  set_point3(xi, yi, pixel_values.get_point3(v, 0));
2277  }
2278  break;
2279 
2280  case 4:
2281  for (int xi = 0; xi < get_x_size(); ++xi) {
2282  int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
2283  nassertv(v >= 0 && v < pixel_values.get_x_size());
2284  set_point4(xi, yi, pixel_values.get_point4(v, 0));
2285  }
2286  break;
2287  }
2288  }
2289 }
2290 
2291 /**
2292  * Adjusts each channel of the image by raising the corresponding component
2293  * value to the indicated exponent, such that L' = L ^ exponent.
2294  */
2295 void PfmFile::
2296 apply_exponent(float c0_exponent, float c1_exponent, float c2_exponent,
2297  float c3_exponent) {
2298  switch (_num_channels) {
2299  case 1:
2300  {
2301  for (int yi = 0; yi < _y_size; ++yi) {
2302  for (int xi = 0; xi < _x_size; ++xi) {
2303  float *val = &_table[(yi * _x_size + xi)];
2304  val[0] = cpow(val[0], c0_exponent);
2305  }
2306  }
2307  }
2308  break;
2309 
2310  case 2:
2311  {
2312  for (int yi = 0; yi < _y_size; ++yi) {
2313  for (int xi = 0; xi < _x_size; ++xi) {
2314  float *val = &_table[(yi * _x_size + xi) * _num_channels];
2315  val[0] = cpow(val[0], c0_exponent);
2316  val[1] = cpow(val[1], c1_exponent);
2317  }
2318  }
2319  }
2320  break;
2321 
2322  case 3:
2323  {
2324  for (int yi = 0; yi < _y_size; ++yi) {
2325  for (int xi = 0; xi < _x_size; ++xi) {
2326  float *val = &_table[(yi * _x_size + xi) * _num_channels];
2327  val[0] = cpow(val[0], c0_exponent);
2328  val[1] = cpow(val[1], c1_exponent);
2329  val[2] = cpow(val[2], c2_exponent);
2330  }
2331  }
2332  }
2333  break;
2334 
2335  case 4:
2336  {
2337  for (int yi = 0; yi < _y_size; ++yi) {
2338  for (int xi = 0; xi < _x_size; ++xi) {
2339  float *val = &_table[(yi * _x_size + xi) * _num_channels];
2340  val[0] = cpow(val[0], c0_exponent);
2341  val[1] = cpow(val[1], c1_exponent);
2342  val[2] = cpow(val[2], c2_exponent);
2343  val[3] = cpow(val[3], c3_exponent);
2344  }
2345  }
2346  }
2347  break;
2348  }
2349 }
2350 
2351 /**
2352  *
2353  */
2354 void PfmFile::
2355 output(ostream &out) const {
2356  out << "floating-point image: " << _x_size << " by " << _y_size << " pixels, "
2357  << _num_channels << " channels.";
2358 }
2359 
2360 /**
2361  * Averages all the points in the rectangle from x0 .. y0 to x1 .. y1 into
2362  * result. The region may be defined by floating-point boundaries; the result
2363  * will be weighted by the degree of coverage of each included point.
2364  */
2365 void PfmFile::
2366 box_filter_region(PN_float32 &result,
2367  PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const {
2368  result = 0.0;
2369  PN_float32 coverage = 0.0;
2370 
2371  if (x1 < x0 || y1 < y0) {
2372  return;
2373  }
2374  nassertv(y0 >= 0.0 && y1 >= 0.0);
2375 
2376  int y = (int)y0;
2377  // Get the first (partial) row
2378  box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0);
2379 
2380  int y_last = (int)y1;
2381  if (y < y_last) {
2382  y++;
2383  while (y < y_last) {
2384  // Get each consecutive (complete) row
2385  box_filter_line(result, coverage, x0, y, x1, 1.0);
2386  y++;
2387  }
2388 
2389  // Get the final (partial) row
2390  PN_float32 y_contrib = y1 - (PN_float32)y_last;
2391  if (y_contrib > 0.0001) {
2392  box_filter_line(result, coverage, x0, y, x1, y_contrib);
2393  }
2394  }
2395 
2396  if (coverage != 0.0) {
2397  result /= coverage;
2398  }
2399 }
2400 
2401 /**
2402  * Averages all the points in the rectangle from x0 .. y0 to x1 .. y1 into
2403  * result. The region may be defined by floating-point boundaries; the result
2404  * will be weighted by the degree of coverage of each included point.
2405  */
2406 void PfmFile::
2407 box_filter_region(LPoint2f &result,
2408  PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const {
2409  result = LPoint2f::zero();
2410  PN_float32 coverage = 0.0;
2411 
2412  if (x1 < x0 || y1 < y0) {
2413  return;
2414  }
2415  nassertv(y0 >= 0.0 && y1 >= 0.0);
2416 
2417  int y = (int)y0;
2418  // Get the first (partial) row
2419  box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0);
2420 
2421  int y_last = (int)y1;
2422  if (y < y_last) {
2423  y++;
2424  while (y < y_last) {
2425  // Get each consecutive (complete) row
2426  box_filter_line(result, coverage, x0, y, x1, 1.0);
2427  y++;
2428  }
2429 
2430  // Get the final (partial) row
2431  PN_float32 y_contrib = y1 - (PN_float32)y_last;
2432  if (y_contrib > 0.0001) {
2433  box_filter_line(result, coverage, x0, y, x1, y_contrib);
2434  }
2435  }
2436 
2437  if (coverage != 0.0) {
2438  result /= coverage;
2439  }
2440 }
2441 
2442 /**
2443  * Averages all the points in the rectangle from x0 .. y0 to x1 .. y1 into
2444  * result. The region may be defined by floating-point boundaries; the result
2445  * will be weighted by the degree of coverage of each included point.
2446  */
2447 void PfmFile::
2448 box_filter_region(LPoint3f &result,
2449  PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const {
2450  result = LPoint3f::zero();
2451  PN_float32 coverage = 0.0;
2452 
2453  if (x1 < x0 || y1 < y0) {
2454  return;
2455  }
2456  nassertv(y0 >= 0.0 && y1 >= 0.0);
2457 
2458  int y = (int)y0;
2459  // Get the first (partial) row
2460  box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0);
2461 
2462  int y_last = (int)y1;
2463  if (y < y_last) {
2464  y++;
2465  while (y < y_last) {
2466  // Get each consecutive (complete) row
2467  box_filter_line(result, coverage, x0, y, x1, 1.0);
2468  y++;
2469  }
2470 
2471  // Get the final (partial) row
2472  PN_float32 y_contrib = y1 - (PN_float32)y_last;
2473  if (y_contrib > 0.0001) {
2474  box_filter_line(result, coverage, x0, y, x1, y_contrib);
2475  }
2476  }
2477 
2478  if (coverage != 0.0) {
2479  result /= coverage;
2480  }
2481 }
2482 
2483 /**
2484  * Averages all the points in the rectangle from x0 .. y0 to x1 .. y1 into
2485  * result. The region may be defined by floating-point boundaries; the result
2486  * will be weighted by the degree of coverage of each included point.
2487  */
2488 void PfmFile::
2489 box_filter_region(LPoint4f &result,
2490  PN_float32 x0, PN_float32 y0, PN_float32 x1, PN_float32 y1) const {
2491  result = LPoint4f::zero();
2492  PN_float32 coverage = 0.0;
2493 
2494  if (x1 < x0 || y1 < y0) {
2495  return;
2496  }
2497  nassertv(y0 >= 0.0 && y1 >= 0.0);
2498 
2499  int y = (int)y0;
2500  // Get the first (partial) row
2501  box_filter_line(result, coverage, x0, y, x1, (PN_float32)(y+1)-y0);
2502 
2503  int y_last = (int)y1;
2504  if (y < y_last) {
2505  y++;
2506  while (y < y_last) {
2507  // Get each consecutive (complete) row
2508  box_filter_line(result, coverage, x0, y, x1, 1.0);
2509  y++;
2510  }
2511 
2512  // Get the final (partial) row
2513  PN_float32 y_contrib = y1 - (PN_float32)y_last;
2514  if (y_contrib > 0.0001) {
2515  box_filter_line(result, coverage, x0, y, x1, y_contrib);
2516  }
2517  }
2518 
2519  if (coverage != 0.0) {
2520  result /= coverage;
2521  }
2522 }
2523 
2524 /**
2525  *
2526  */
2527 void PfmFile::
2528 box_filter_line(PN_float32 &result, PN_float32 &coverage,
2529  PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const {
2530  int x = (int)x0;
2531  // Get the first (partial) xel
2532  box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib);
2533 
2534  int x_last = (int)x1;
2535  if (x < x_last) {
2536  x++;
2537  while (x < x_last) {
2538  // Get each consecutive (complete) xel
2539  box_filter_point(result, coverage, x, y, 1.0, y_contrib);
2540  x++;
2541  }
2542 
2543  // Get the final (partial) xel
2544  PN_float32 x_contrib = x1 - (PN_float32)x_last;
2545  if (x_contrib > 0.0001) {
2546  box_filter_point(result, coverage, x, y, x_contrib, y_contrib);
2547  }
2548  }
2549 }
2550 
2551 /**
2552  *
2553  */
2554 void PfmFile::
2555 box_filter_line(LPoint2f &result, PN_float32 &coverage,
2556  PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const {
2557  int x = (int)x0;
2558  // Get the first (partial) xel
2559  box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib);
2560 
2561  int x_last = (int)x1;
2562  if (x < x_last) {
2563  x++;
2564  while (x < x_last) {
2565  // Get each consecutive (complete) xel
2566  box_filter_point(result, coverage, x, y, 1.0, y_contrib);
2567  x++;
2568  }
2569 
2570  // Get the final (partial) xel
2571  PN_float32 x_contrib = x1 - (PN_float32)x_last;
2572  if (x_contrib > 0.0001) {
2573  box_filter_point(result, coverage, x, y, x_contrib, y_contrib);
2574  }
2575  }
2576 }
2577 
2578 /**
2579  *
2580  */
2581 void PfmFile::
2582 box_filter_line(LPoint3f &result, PN_float32 &coverage,
2583  PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const {
2584  int x = (int)x0;
2585  // Get the first (partial) xel
2586  box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib);
2587 
2588  int x_last = (int)x1;
2589  if (x < x_last) {
2590  x++;
2591  while (x < x_last) {
2592  // Get each consecutive (complete) xel
2593  box_filter_point(result, coverage, x, y, 1.0, y_contrib);
2594  x++;
2595  }
2596 
2597  // Get the final (partial) xel
2598  PN_float32 x_contrib = x1 - (PN_float32)x_last;
2599  if (x_contrib > 0.0001) {
2600  box_filter_point(result, coverage, x, y, x_contrib, y_contrib);
2601  }
2602  }
2603 }
2604 
2605 /**
2606  *
2607  */
2608 void PfmFile::
2609 box_filter_line(LPoint4f &result, PN_float32 &coverage,
2610  PN_float32 x0, int y, PN_float32 x1, PN_float32 y_contrib) const {
2611  int x = (int)x0;
2612  // Get the first (partial) xel
2613  box_filter_point(result, coverage, x, y, (PN_float32)(x+1)-x0, y_contrib);
2614 
2615  int x_last = (int)x1;
2616  if (x < x_last) {
2617  x++;
2618  while (x < x_last) {
2619  // Get each consecutive (complete) xel
2620  box_filter_point(result, coverage, x, y, 1.0, y_contrib);
2621  x++;
2622  }
2623 
2624  // Get the final (partial) xel
2625  PN_float32 x_contrib = x1 - (PN_float32)x_last;
2626  if (x_contrib > 0.0001) {
2627  box_filter_point(result, coverage, x, y, x_contrib, y_contrib);
2628  }
2629  }
2630 }
2631 
2632 /**
2633  *
2634  */
2635 void PfmFile::
2636 box_filter_point(PN_float32 &result, PN_float32 &coverage,
2637  int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const {
2638  if (!has_point(x, y)) {
2639  return;
2640  }
2641  PN_float32 point = get_point1(x, y);
2642 
2643  PN_float32 contrib = x_contrib * y_contrib;
2644  result += point * contrib;
2645  coverage += contrib;
2646 }
2647 
2648 /**
2649  *
2650  */
2651 void PfmFile::
2652 box_filter_point(LPoint2f &result, PN_float32 &coverage,
2653  int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const {
2654  if (!has_point(x, y)) {
2655  return;
2656  }
2657  const LPoint2f &point = get_point2(x, y);
2658 
2659  PN_float32 contrib = x_contrib * y_contrib;
2660  result += point * contrib;
2661  coverage += contrib;
2662 }
2663 
2664 /**
2665  *
2666  */
2667 void PfmFile::
2668 box_filter_point(LPoint3f &result, PN_float32 &coverage,
2669  int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const {
2670  if (!has_point(x, y)) {
2671  return;
2672  }
2673  const LPoint3f &point = get_point3(x, y);
2674 
2675  PN_float32 contrib = x_contrib * y_contrib;
2676  result += point * contrib;
2677  coverage += contrib;
2678 }
2679 
2680 /**
2681  *
2682  */
2683 void PfmFile::
2684 box_filter_point(LPoint4f &result, PN_float32 &coverage,
2685  int x, int y, PN_float32 x_contrib, PN_float32 y_contrib) const {
2686  if (!has_point(x, y)) {
2687  return;
2688  }
2689  const LPoint4f &point = get_point4(x, y);
2690 
2691  PN_float32 contrib = x_contrib * y_contrib;
2692  result += point * contrib;
2693  coverage += contrib;
2694 }
2695 
2696 /**
2697  * A support function for calc_average_point(), this recursively fills in the
2698  * holes in the mini_grid data with the index to the nearest value.
2699  */
2700 void PfmFile::
2701 fill_mini_grid(MiniGridCell *mini_grid, int x_size, int y_size,
2702  int xi, int yi, int dist, int sxi, int syi) const {
2703  if (xi < 0 || xi >= x_size || yi < 0 || yi >= y_size) {
2704  // Out of bounds.
2705  return;
2706  }
2707 
2708  int gi = yi * x_size + xi;
2709  if (mini_grid[gi]._dist == -1 || mini_grid[gi]._dist > dist) {
2710  // Here's an undefined value that we need to populate.
2711  mini_grid[gi]._dist = dist;
2712  mini_grid[gi]._sxi = sxi;
2713  mini_grid[gi]._syi = syi;
2714  fill_mini_grid(mini_grid, x_size, y_size, xi + 1, yi, dist + 1, sxi, syi);
2715  fill_mini_grid(mini_grid, x_size, y_size, xi - 1, yi, dist + 1, sxi, syi);
2716  fill_mini_grid(mini_grid, x_size, y_size, xi, yi + 1, dist + 1, sxi, syi);
2717  fill_mini_grid(mini_grid, x_size, y_size, xi, yi - 1, dist + 1, sxi, syi);
2718  }
2719 }
2720 
2721 /**
2722  * The implementation of has_point() for files without a no_data_value.
2723  */
2724 bool PfmFile::
2725 has_point_noop(const PfmFile *self, int x, int y) {
2726  if ((x >= 0 && x < self->_x_size) &&
2727  (y >= 0 && y < self->_y_size)) {
2728  return true;
2729  }
2730  return false;
2731 }
2732 
2733 /**
2734  * The implementation of has_point() for 1-component files with a
2735  * no_data_value.
2736  */
2737 bool PfmFile::
2738 has_point_1(const PfmFile *self, int x, int y) {
2739  if ((x >= 0 && x < self->_x_size) &&
2740  (y >= 0 && y < self->_y_size)) {
2741  return self->_table[(y * self->_x_size + x)] != self->_no_data_value[0];
2742  }
2743  return false;
2744 }
2745 
2746 /**
2747  * The implementation of has_point() for 2-component files with a
2748  * no_data_value.
2749  */
2750 bool PfmFile::
2751 has_point_2(const PfmFile *self, int x, int y) {
2752  if ((x >= 0 && x < self->_x_size) &&
2753  (y >= 0 && y < self->_y_size)) {
2754  return *(LPoint2f *)&self->_table[(y * self->_x_size + x) * 2] != *(LPoint2f *)&self->_no_data_value;
2755  }
2756  return false;
2757 }
2758 
2759 /**
2760  * The implementation of has_point() for 3-component files with a
2761  * no_data_value.
2762  */
2763 bool PfmFile::
2764 has_point_3(const PfmFile *self, int x, int y) {
2765  if ((x >= 0 && x < self->_x_size) &&
2766  (y >= 0 && y < self->_y_size)) {
2767  return *(LPoint3f *)&self->_table[(y * self->_x_size + x) * 3] != *(LPoint3f *)&self->_no_data_value;
2768  }
2769  return false;
2770 }
2771 
2772 /**
2773  * The implementation of has_point() for 4-component files with a
2774  * no_data_value.
2775  */
2776 bool PfmFile::
2777 has_point_4(const PfmFile *self, int x, int y) {
2778  if ((x >= 0 && x < self->_x_size) &&
2779  (y >= 0 && y < self->_y_size)) {
2780  return *(LPoint4f *)&self->_table[(y * self->_x_size + x) * 4] != *(LPoint4f *)&self->_no_data_value;
2781  }
2782  return false;
2783 }
2784 
2785 /**
2786  * The implementation of has_point_threshold() for 1-component files with a
2787  * no_data_value.
2788  */
2789 bool PfmFile::
2790 has_point_threshold_1(const PfmFile *self, int x, int y) {
2791  if ((x >= 0 && x < self->_x_size) &&
2792  (y >= 0 && y < self->_y_size)) {
2793  const float *table = &self->_table[(y * self->_x_size + x)];
2794  return table[0] >= self->_no_data_value[0];
2795  }
2796  return false;
2797 }
2798 
2799 /**
2800  * The implementation of has_point_threshold() for 2-component files with a
2801  * no_data_value.
2802  */
2803 bool PfmFile::
2804 has_point_threshold_2(const PfmFile *self, int x, int y) {
2805  if ((x >= 0 && x < self->_x_size) &&
2806  (y >= 0 && y < self->_y_size)) {
2807  const float *table = &self->_table[(y * self->_x_size + x) * 2];
2808  return (table[0] >= self->_no_data_value[0] ||
2809  table[1] >= self->_no_data_value[1]);
2810  }
2811  return false;
2812 }
2813 
2814 /**
2815  * The implementation of has_point_threshold() for 3-component files with a
2816  * no_data_value.
2817  */
2818 bool PfmFile::
2819 has_point_threshold_3(const PfmFile *self, int x, int y) {
2820  if ((x >= 0 && x < self->_x_size) &&
2821  (y >= 0 && y < self->_y_size)) {
2822  const float *table = &self->_table[(y * self->_x_size + x) * 3];
2823  return (table[0] >= self->_no_data_value[0] ||
2824  table[1] >= self->_no_data_value[1] ||
2825  table[2] >= self->_no_data_value[2]);
2826  }
2827  return false;
2828 }
2829 
2830 /**
2831  * The implementation of has_point_threshold() for 4-component files with a
2832  * no_data_value.
2833  */
2834 bool PfmFile::
2835 has_point_threshold_4(const PfmFile *self, int x, int y) {
2836  if ((x >= 0 && x < self->_x_size) &&
2837  (y >= 0 && y < self->_y_size)) {
2838  const float *table = &self->_table[(y * self->_x_size + x) * 4];
2839  return (table[0] >= self->_no_data_value[0] ||
2840  table[1] >= self->_no_data_value[1] ||
2841  table[2] >= self->_no_data_value[2] ||
2842  table[3] >= self->_no_data_value[3]);
2843  }
2844  return false;
2845 }
2846 
2847 /**
2848  * The implementation of has_point() for 4-component files with
2849  * set_no_data_chan4() in effect. This means that the data is valid iff the
2850  * fourth channel >= 0.
2851  */
2852 bool PfmFile::
2853 has_point_chan4(const PfmFile *self, int x, int y) {
2854  if ((x >= 0 && x < self->_x_size) &&
2855  (y >= 0 && y < self->_y_size)) {
2856  return self->_table[(y * self->_x_size + x) * 4 + 3] >= 0.0;
2857  }
2858  return false;
2859 }
2860 
2861 /**
2862  * The implementation of has_point() for files with set_no_data_nan() in
2863  * effect. This means that the data is valid iff no components involve NaN.
2864  */
2865 bool PfmFile::
2866 has_point_nan_1(const PfmFile *self, int x, int y) {
2867  if ((x >= 0 && x < self->_x_size) &&
2868  (y >= 0 && y < self->_y_size)) {
2869  return !cnan(self->_table[(y * self->_x_size + x) * self->_num_channels]);
2870  }
2871  return false;
2872 }
2873 
2874 /**
2875  * The implementation of has_point() for files with set_no_data_nan() in
2876  * effect. This means that the data is valid iff no components involve NaN.
2877  */
2878 bool PfmFile::
2879 has_point_nan_2(const PfmFile *self, int x, int y) {
2880  if ((x >= 0 && x < self->_x_size) &&
2881  (y >= 0 && y < self->_y_size)) {
2882  return !((LVecBase2f *)&self->_table[(y * self->_x_size + x) * self->_num_channels])->is_nan();
2883  }
2884  return false;
2885 }
2886 
2887 /**
2888  * The implementation of has_point() for files with set_no_data_nan() in
2889  * effect. This means that the data is valid iff no components involve NaN.
2890  */
2891 bool PfmFile::
2892 has_point_nan_3(const PfmFile *self, int x, int y) {
2893  if ((x >= 0 && x < self->_x_size) &&
2894  (y >= 0 && y < self->_y_size)) {
2895  return !((LVecBase3f *)&self->_table[(y * self->_x_size + x) * self->_num_channels])->is_nan();
2896  }
2897  return false;
2898 }
2899 
2900 /**
2901  * The implementation of has_point() for files with set_no_data_nan() in
2902  * effect. This means that the data is valid iff no components involve NaN.
2903  */
2904 bool PfmFile::
2905 has_point_nan_4(const PfmFile *self, int x, int y) {
2906  if ((x >= 0 && x < self->_x_size) &&
2907  (y >= 0 && y < self->_y_size)) {
2908  return !((LVecBase4f *)&self->_table[(y * self->_x_size + x) * self->_num_channels])->is_nan();
2909  }
2910  return false;
2911 }
bool calc_average_point(LPoint3f &result, PN_float32 x, PN_float32 y, PN_float32 radius) const
Computes the unweighted average point of all points within the box centered at (x,...
Definition: pfmFile.cxx:607
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
Definition: filename.cxx:1899
void apply_exponent(float gray_exponent)
Adjusts each channel of the image by raising the corresponding component value to the indicated expon...
Definition: pfmFile.I:508
void forward_distort(const PfmFile &dist, PN_float32 scale_factor=1.0)
Applies the distortion indicated in the supplied dist map to the current map.
Definition: pfmFile.cxx:1305
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool has_point(int x, int y) const
Returns true if there is a valid point at x, y.
Definition: pfmFile.I:44
void copy_header_from(const PNMImageHeader &header)
Initializes all the data in the header (x_size, y_size, num_channels, etc.) to the same values indica...
Definition: pnmWriter.I:83
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMReader * make_reader(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true) const
Returns a newly-allocated PNMReader of the suitable type for reading from the indicated image filenam...
bool is_column_empty(int x, int y_begin, int y_end) const
Returns true if all of the points on column x, from [y_begin, y_end), are the no_data value,...
Definition: pfmFile.cxx:834
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_num_channels
Returns the number of channels in the image.
PNMWriter * make_writer(const Filename &filename, PNMFileType *type=nullptr) const
Returns a newly-allocated PNMWriter of the suitable type for writing an image to the indicated filena...
void fill_channel_masked(int channel, PN_float32 value)
Fills the indicated channel with all of the same value, but only where the table already has a data p...
Definition: pfmFile.cxx:574
void mult_sub_image(const PfmFile &copy, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the copy pixels are multiplied to the pixels of the destination...
Definition: pfmFile.cxx:2029
void fill_channel(int channel, PN_float32 value)
Fills the indicated channel with all of the same value, leaving the other channels unchanged.
Definition: pfmFile.cxx:550
void set_point4(int x, int y, const LVecBase4f &point)
Replaces the 4-component point value at the indicated point.
Definition: pfmFile.I:266
A hierarchy of directories and files that appears to be one continuous file system,...
bool calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point) const
Calculates the minimum and maximum vertices of all points within the table.
Definition: pfmFile.cxx:1646
const LPoint3f & get_point(int x, int y) const
Returns the 3-component point value at the indicated point.
Definition: pfmFile.I:158
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:785
void apply_1d_lut(int channel, const PfmFile &lut, PN_float32 x_scale=1.0)
Assumes that lut is an X by 1, 1-component PfmFile whose X axis maps points to target points.
Definition: pfmFile.cxx:1438
virtual bool write_pfm(const PfmFile &pfm)
Writes floating-point data from the indicated PfmFile.
Definition: pnmWriter.cxx:53
virtual bool is_floating_point()
Returns true if this PNMFileType represents a floating-point image type, false if it is a normal,...
Definition: pnmReader.cxx:71
bool calc_bilinear_point(LPoint3f &result, PN_float32 x, PN_float32 y) const
Computes the weighted average of the four nearest points to the floating- point index (x,...
Definition: pfmFile.cxx:689
void apply_mask(const PfmFile &other)
Wherever there is missing data in the other PfmFile, set this the corresponding point in this PfmFile...
Definition: pfmFile.cxx:1490
void fill_nan()
Fills the table with all NaN.
Definition: pfmFile.cxx:531
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void fill_channel_masked_nan(int channel)
Fills the indicated channel with NaN, but only where the table already has a data point.
Definition: pfmFile.cxx:595
void fill_channel_nan(int channel)
Fills the indicated channel with NaN, leaving the other channels unchanged.
Definition: pfmFile.cxx:564
xel * get_array()
Directly access the underlying PNMImage array.
Definition: pnmImage.I:1084
void set_point2(int x, int y, const LVecBase2f &point)
Replaces the 2-component point value at the indicated point.
Definition: pfmFile.I:107
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const LPoint4f & get_point4(int x, int y) const
Returns the 4-component point value at the indicated point.
Definition: pfmFile.I:255
void set_point3(int x, int y, const LVecBase3f &point)
Replaces the 3-component point value at the indicated point.
Definition: pfmFile.I:204
virtual bool supports_floating_point()
Returns true if this PNMFileType can accept a floating-point image type, false if it can only accept ...
Definition: pnmWriter.cxx:34
bool is_row_empty(int y, int x_begin, int x_end) const
Returns true if all of the points on row y, in the range [x_begin, x_end), are the no_data value,...
Definition: pfmFile.cxx:813
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_x_size() const
Returns the number of pixels in the X direction.
const LPoint3f & get_point3(int x, int y) const
Returns the 3-component point value at the indicated point.
Definition: pfmFile.I:193
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition: pnmReader.cxx:80
void resize(int new_x_size, int new_y_size)
Applies a simple filter to resample the pfm file in-place to the indicated size.
Definition: pfmFile.cxx:956
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
Definition: pnmImage.I:253
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
const LPoint2f & get_point2(int x, int y) const
Returns the 2-component point value at the indicated point.
Definition: pfmFile.I:96
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
LPoint4f & modify_point4(int x, int y)
Returns a modifiable 4-component point value at the indicated point.
Definition: pfmFile.I:302
PN_float32 get_point1(int x, int y) const
Returns the 1-component point value at the indicated point.
Definition: pfmFile.I:74
bool read(const Filename &fullpath)
Reads the PFM data from the indicated file, returning true on success, false on failure.
Definition: pfmFile.cxx:121
void set_point1(int x, int y, PN_float32 point)
Replaces the 1-component point value at the indicated point.
Definition: pfmFile.I:84
bool write(const Filename &fullpath)
Writes the PFM data to the indicated file, returning true on success, false on failure.
Definition: pfmFile.cxx:204
bool is_valid() const
Returns true if the PNMReader can be used to read data, false if something is wrong.
Definition: pnmReader.I:53
void gaussian_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
void merge(const PfmFile &other)
Wherever there is missing data in this PfmFile (that is, wherever has_point() returns false),...
Definition: pfmFile.cxx:1461
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition: pnmImage.I:480
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:594
void copy_channel_masked(int to_channel, const PfmFile &other, int from_channel)
Copies just the specified channel values from the indicated PfmFile, but only where the other file ha...
Definition: pfmFile.cxx:1531
void apply_crop(int x_begin, int x_end, int y_begin, int y_end)
Reduces the PFM file to the cells in the rectangle bounded by (x_begin, x_end, y_begin,...
Definition: pfmFile.cxx:1551
LPoint3f & modify_point3(int x, int y)
Returns a modifiable 3-component point value at the indicated point.
Definition: pfmFile.I:240
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void reverse_rows()
Performs an in-place reversal of the row (y) data.
Definition: pfmFile.cxx:1146
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
Definition: pnmImage.I:835
void box_filter_from(float radius, const PfmFile &copy)
Makes a resized copy of the indicated image into this one using the indicated filter.
void clear_to_texcoords(int x_size, int y_size)
Replaces this PfmFile with a new PfmFile of size x_size x y_size x 3, containing the x y 0 values in ...
Definition: pfmFile.cxx:1582
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PN_float32 get_channel(int x, int y, int c) const
Returns the cth channel of the point value at the indicated point.
Definition: pfmFile.I:52
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition: pnmImage.I:661
void reverse_distort(const PfmFile &dist, PN_float32 scale_factor=1.0)
Applies the distortion indicated in the supplied dist map to the current map.
Definition: pfmFile.cxx:1378
void fill(PN_float32 value)
Fills the table with all of the same value.
Definition: pfmFile.I:316
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
void set_point(int x, int y, const LVecBase3f &point)
Replaces the 3-component point value at the indicated point.
Definition: pfmFile.I:167
void flip(bool flip_x, bool flip_y, bool transpose)
Reverses, transposes, and/or rotates the table in-place according to the specified parameters.
Definition: pfmFile.cxx:1174
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
xelval * get_alpha_array()
Directly access the underlying PNMImage array of alpha values.
Definition: pnmImage.I:1101
void quick_filter_from(const PfmFile &copy)
Resizes from the given image, with a fixed radius of 0.5.
Definition: pfmFile.cxx:998
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
This is an abstract base class that defines the interface for writing image files of various types.
Definition: pnmWriter.h:27
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool store_mask(PNMImage &pnmimage) const
Stores 1 or 0 values into the indicated PNMImage, according to has_point() for each pixel.
Definition: pfmFile.cxx:426
void indirect_1d_lookup(const PfmFile &index_image, int channel, const PfmFile &pixel_values)
index_image is a WxH 1-channel image, while pixel_values is an Nx1 image with any number of channels.
Definition: pfmFile.cxx:2249
void set_channel(int x, int y, int c, PN_float32 value)
Replaces the cth channel of the point value at the indicated point.
Definition: pfmFile.I:63
bool load(const PNMImage &pnmimage)
Fills the PfmFile with the data from the indicated PNMImage, converted to floating-point values.
Definition: pfmFile.cxx:287
int pull_spot(const LPoint4f &delta, float xc, float yc, float xr, float yr, float exponent)
Applies delta * t to the point values within radius (xr, yr) distance of (xc, yc).
Definition: pfmFile.cxx:1610
virtual int write_data(xel *array, xelval *alpha)
Writes out an entire image all at once, including the header, based on the image data stored in the g...
Definition: pnmWriter.cxx:74
void operator *=(float multiplier)
Multiplies every point value in the image by a constant floating-point multiplier value.
Definition: pfmFile.cxx:2174
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:845
void xform(const LMatrix4f &transform)
Applies the indicated transform matrix to all points in-place.
Definition: pfmFile.cxx:1222
void divide_sub_image(const PfmFile &copy, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the copy pixels are divided into the pixels of the destination,...
Definition: pfmFile.cxx:2094
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void copy_channel(int to_channel, const PfmFile &other, int from_channel)
Copies just the specified channel values from the indicated PfmFile (which could be same as this PfmF...
Definition: pfmFile.cxx:1513
This is the base class of PNMImage, PNMReader, and PNMWriter.
void clear_no_data_value()
Removes the special value that means "no data" when it appears in the pfm file.
Definition: pfmFile.I:421
bool calc_min_max(LVecBase3f &min_points, LVecBase3f &max_points) const
Calculates the minimum and maximum x, y, and z depth component values, representing the bounding box ...
Definition: pfmFile.cxx:737
void set_no_data_threshold(const LPoint4f &no_data_value)
Sets the special threshold value.
Definition: pfmFile.cxx:925
void fill_no_data_value()
Fills the table with the current no_data value, so that the table is empty.
Definition: pfmFile.cxx:541
LPoint2f & modify_point2(int x, int y)
Returns a modifiable 2-component point value at the indicated point.
Definition: pfmFile.I:143
bool store(PNMImage &pnmimage) const
Copies the data to the indicated PNMImage, converting to RGB values.
Definition: pfmFile.cxx:360
void add_sub_image(const PfmFile &copy, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1, float pixel_scale=1.0)
Behaves like copy_sub_image(), except the copy pixels are added to the pixels of the destination,...
Definition: pfmFile.cxx:1965
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool calc_autocrop(int &x_begin, int &x_end, int &y_begin, int &y_end) const
Computes the minimum range of x and y across the PFM file that include all points.
Definition: pfmFile.cxx:775
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:522
void set_no_data_nan(int num_channels)
Sets the no_data_nan flag.
Definition: pfmFile.cxx:858
void clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77
This defines a bounding convex hexahedron.
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:795
void set_no_data_value(const LPoint4f &no_data_value)
Sets the special value that means "no data" when it appears in the pfm file.
Definition: pfmFile.cxx:895
PT(BoundingHexahedron) PfmFile
Computes the minmax bounding volume of the points in 3-D space, assuming the points represent a mostl...
Definition: pfmFile.cxx:1690