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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
531 fill_nan() {
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  */
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  */
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  */
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  */
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  */
595 fill_channel_masked_nan(int channel) {
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
998 quick_filter_from(const PfmFile &from) {
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  */
1146 reverse_rows() {
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This defines a bounding convex hexahedron.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
Definition: filename.cxx:1899
This is the base class of PNMImage, PNMReader, and PNMWriter.
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...
int get_x_size() const
Returns the number of pixels in the X direction.
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...
get_num_channels
Returns the number of channels in the image.
int get_y_size() const
Returns the number of pixels in the Y direction.
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
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition: pnmImage.cxx:48
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition: pnmImage.I:809
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition: pnmImage.I:569
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition: pnmImage.I:675
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition: pnmImage.I:799
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition: pnmImage.I:608
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
Definition: pnmImage.I:849
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
Definition: pnmImage.I:342
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
xel * get_array()
Directly access the underlying PNMImage array.
Definition: pnmImage.I:1098
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:579
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:859
xelval * get_alpha_array()
Directly access the underlying PNMImage array of alpha values.
Definition: pnmImage.I:1115
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
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 is_valid() const
Returns true if the PNMReader can be used to read data, false if something is wrong.
Definition: pnmReader.I:53
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition: pnmReader.cxx:80
This is an abstract base class that defines the interface for writing image files of various types.
Definition: pnmWriter.h:27
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
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
virtual bool write_pfm(const PfmFile &pfm)
Writes floating-point data from the indicated PfmFile.
Definition: pnmWriter.cxx:53
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
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
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
void set_no_data_nan(int num_channels)
Sets the no_data_nan flag.
Definition: pfmFile.cxx:858
void clear_no_data_value()
Removes the special value that means "no data" when it appears in the pfm file.
Definition: pfmFile.I:421
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
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 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 merge(const PfmFile &other)
Wherever there is missing data in this PfmFile (that is, wherever has_point() returns false),...
Definition: pfmFile.cxx:1461
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
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
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 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
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
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
const LPoint3f & get_point(int x, int y) const
Returns the 3-component point value at the indicated point.
Definition: pfmFile.I:158
void reverse_rows()
Performs an in-place reversal of the row (y) data.
Definition: pfmFile.cxx:1146
bool read(const Filename &fullpath)
Reads the PFM data from the indicated file, returning true on success, false on failure.
Definition: pfmFile.cxx:121
bool write(const Filename &fullpath)
Writes the PFM data to the indicated file, returning true on success, false on failure.
Definition: pfmFile.cxx:204
void set_no_data_threshold(const LPoint4f &no_data_value)
Sets the special threshold value.
Definition: pfmFile.cxx:925
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
void fill_channel_nan(int channel)
Fills the indicated channel with NaN, leaving the other channels unchanged.
Definition: pfmFile.cxx:564
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
bool store(PNMImage &pnmimage) const
Copies the data to the indicated PNMImage, converting to RGB values.
Definition: pfmFile.cxx:360
void xform(const LMatrix4f &transform)
Applies the indicated transform matrix to all points in-place.
Definition: pfmFile.cxx:1222
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
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
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
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 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
bool load(const PNMImage &pnmimage)
Fills the PfmFile with the data from the indicated PNMImage, converted to floating-point values.
Definition: pfmFile.cxx:287
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 set_point4(int x, int y, const LVecBase4f &point)
Replaces the 4-component point value at the indicated point.
Definition: pfmFile.I:266
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
void fill(PN_float32 value)
Fills the table with all of the same value.
Definition: pfmFile.I:316
LPoint3f & modify_point3(int x, int y)
Returns a modifiable 3-component point value at the indicated point.
Definition: pfmFile.I:240
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_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 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_point2(int x, int y, const LVecBase2f &point)
Replaces the 2-component point value at the indicated point.
Definition: pfmFile.I:107
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 quick_filter_from(const PfmFile &copy)
Resizes from the given image, with a fixed radius of 0.5.
Definition: pfmFile.cxx:998
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 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
LPoint4f & modify_point4(int x, int y)
Returns a modifiable 4-component point value at the indicated point.
Definition: pfmFile.I:302
LPoint2f & modify_point2(int x, int y)
Returns a modifiable 2-component point value at the indicated point.
Definition: pfmFile.I:143
const LPoint3f & get_point3(int x, int y) const
Returns the 3-component point value at the indicated point.
Definition: pfmFile.I:193
void set_point3(int x, int y, const LVecBase3f &point)
Replaces the 3-component point value at the indicated point.
Definition: pfmFile.I:204
void operator*=(float multiplier)
Multiplies every point value in the image by a constant floating-point multiplier value.
Definition: pfmFile.cxx:2174
void fill_nan()
Fills the table with all NaN.
Definition: pfmFile.cxx:531
const LPoint2f & get_point2(int x, int y) const
Returns the 2-component point value at the indicated point.
Definition: pfmFile.I:96
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
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
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
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
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 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 clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77
void fill_no_data_value()
Fills the table with the current no_data value, so that the table is empty.
Definition: pfmFile.cxx:541
void set_point1(int x, int y, PN_float32 point)
Replaces the 1-component point value at the indicated point.
Definition: pfmFile.I:84
PN_float32 get_point1(int x, int y) const
Returns the 1-component point value at the indicated point.
Definition: pfmFile.I:74
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 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
bool has_point(int x, int y) const
Returns true if there is a valid point at x, y.
Definition: pfmFile.I:44
const LPoint4f & get_point4(int x, int y) const
Returns the 4-component point value at the indicated point.
Definition: pfmFile.I:255
A hierarchy of directories and files that appears to be one continuous file system,...
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:35
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.