Panda3D
Loading...
Searching...
No Matches
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
27using std::istream;
28using std::max;
29using std::min;
30using std::ostream;
31
32/**
33 *
34 */
35PfmFile::
36PfmFile() {
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 */
47PfmFile::
48PfmFile(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 */
62void PfmFile::
63operator = (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 */
77clear() {
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 */
92clear(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 */
121read(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 */
153read(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 */
168read(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 */
204write(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 */
232write(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 */
251write(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 */
287load(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 */
360store(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 */
426store_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 */
452store_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 */
483fill(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 */
531fill_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 */
550fill_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 */
564fill_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 */
574fill_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 */
595fill_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 */
607calc_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 */
689calc_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 */
737calc_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 */
775calc_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 */
813is_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 */
834is_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 */
858set_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 */
895set_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 */
925set_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 */
956resize(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 */
998quick_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 */
1146reverse_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 */
1174flip(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 */
1222xform(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 */
1305forward_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 */
1378reverse_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 */
1438apply_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 */
1461merge(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 */
1490apply_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 */
1513copy_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 */
1531copy_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 */
1551apply_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 */
1582clear_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 */
1610pull_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 */
1646calc_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 */
1691compute_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 */
1851void PfmFile::
1852compute_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 */
1901void PfmFile::
1902copy_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 */
1965add_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 */
2029mult_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 */
2094divide_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 */
2174operator *= (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 */
2249indirect_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 */
2296apply_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 */
2354void PfmFile::
2355output(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 */
2365void PfmFile::
2366box_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 */
2406void PfmFile::
2407box_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 */
2447void PfmFile::
2448box_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 */
2488void PfmFile::
2489box_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 */
2527void PfmFile::
2528box_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 */
2554void PfmFile::
2555box_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 */
2581void PfmFile::
2582box_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 */
2608void PfmFile::
2609box_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 */
2635void PfmFile::
2636box_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 */
2651void PfmFile::
2652box_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 */
2667void PfmFile::
2668box_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 */
2683void PfmFile::
2684box_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 */
2700void PfmFile::
2701fill_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 */
2724bool PfmFile::
2725has_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 */
2737bool PfmFile::
2738has_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 */
2750bool PfmFile::
2751has_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 */
2763bool PfmFile::
2764has_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 */
2776bool PfmFile::
2777has_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 */
2789bool PfmFile::
2790has_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 */
2803bool PfmFile::
2804has_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 */
2818bool PfmFile::
2819has_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 */
2834bool PfmFile::
2835has_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 */
2852bool PfmFile::
2853has_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 */
2865bool PfmFile::
2866has_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 */
2878bool PfmFile::
2879has_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 */
2891bool PfmFile::
2892has_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 */
2904bool PfmFile::
2905has_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:44
bool open_write(std::ofstream &stream, bool truncate=true) const
Opens the indicated ifstream for writing the file, if possible.
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.
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.