Panda3D

Loading...
Searching...
No Matches
pnmImage.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 pnmImage.cxx
10 * @author drose
11 * @date 2000-06-14
12 */
13
14#include "pnmImage.h"
15#include "pnmReader.h"
16#include "pnmWriter.h"
17#include "pnmBrush.h"
18#include "pfmFile.h"
19#include "config_pnmimage.h"
20#include "perlinNoise2.h"
21#include "stackedPerlinNoise2.h"
22#include <algorithm>
23
24 using std::max;
25 using std::min;
26
27/**
28 *
29 */
30PNMImage::
31PNMImage(const Filename &filename, PNMFileType *type) {
32 _array = nullptr;
33 _alpha = nullptr;
34 _has_read_size = false;
35
36 bool result = read(filename, type);
37 if (!result) {
38 pnmimage_cat.error()
39 << "Could not read image " << filename << "\n";
40 }
41}
42
43/**
44 * Frees all memory allocated for the image, and clears all its parameters
45 * (size, color, type, etc).
46 */
47 void PNMImage::
48 clear() {
49 if (_array != nullptr) {
50 PANDA_FREE_ARRAY(_array);
51 _array = nullptr;
52 }
53 if (_alpha != nullptr) {
54 PANDA_FREE_ARRAY(_alpha);
55 _alpha = nullptr;
56 }
57 _x_size = 0;
58 _y_size = 0;
59 _num_channels = 0;
60 _maxval = 255;
61 _inv_maxval = 1.0 / 255.0;
62 _color_space = CS_linear;
63 _comment.clear();
64 _type = nullptr;
65 _has_read_size = false;
66 _xel_encoding = XE_generic;
67}
68
69/**
70 * This flavor of clear() reinitializes the image to an empty (black) image
71 * with the given dimensions.
72 */
73 void PNMImage::
74 clear(int x_size, int y_size, int num_channels,
75 xelval maxval, PNMFileType *type, ColorSpace color_space) {
76 clear();
77 nassertv(num_channels >= 1 && num_channels <= 4);
78 nassertv(color_space != CS_unspecified);
79
80 _x_size = x_size;
81 _y_size = y_size;
82 _num_channels = num_channels;
83 _maxval = maxval;
84 _color_space = color_space;
85 _comment.clear();
86 _type = type;
87 _has_read_size = false;
88
89 if (has_alpha()) {
90 allocate_alpha();
91 memset(_alpha, 0, sizeof(xelval) * _y_size * _x_size);
92 }
93
94 allocate_array();
95 memset(_array, 0, sizeof(xel) * _y_size * _x_size);
96
97 setup_encoding();
98 setup_rc();
99}
100
101/**
102 * Makes this image become a copy of the other image.
103 */
104 void PNMImage::
105 copy_from(const PNMImage &copy) {
106 clear();
107 copy_header_from(copy);
108
109 if (copy.is_valid()) {
110 if (has_alpha()) {
111 memcpy(_alpha, copy._alpha, sizeof(xelval) * _y_size * _x_size);
112 }
113 memcpy(_array, copy._array, sizeof(xel) * _y_size * _x_size);
114 }
115}
116
117/**
118 * Copies a channel from one image into another. Images must be the same size
119 */
120 void PNMImage::
121 copy_channel(const PNMImage &copy, int src_channel, int dest_channel) {
122 // Make sure the channels are in range
123 nassertv(src_channel >= 0 && src_channel <= 3);
124 nassertv(dest_channel >= 0 && dest_channel <= 3);
125 // Make sure that images are valid
126 if (!copy.is_valid() || !is_valid()) {
127 pnmimage_cat.error() << "One of the images is invalid!\n";
128 return;
129 }
130 // Make sure the images are the same size
131 if (_x_size != copy.get_x_size() || _y_size != copy.get_y_size()){
132 pnmimage_cat.error() << "Image size doesn't match!\n";
133 return;
134 }
135 // Do the actual copying
136 for (int x = 0; x < _x_size; x++) {
137 for (int y = 0; y < _y_size; y++) {
138 LColorf t = get_xel_a(x, y);
139 LColorf o = copy.get_xel_a(x, y);
140 t.set_cell(dest_channel, o.get_cell(src_channel));
141 set_xel_a(x, y, t);
142 }
143 }
144}
145
146/**
147 * Copies some subset of the bits of the specified channel from one image into
148 * some subset of the bits of the specified channel in another image. Images
149 * must be the same size.
150 *
151 * If right_shift is negative, it means a left shift.
152 */
153 void PNMImage::
154 copy_channel_bits(const PNMImage &copy, int src_channel, int dest_channel, xelval src_mask, int right_shift) {
155 // Make sure the channels are in range
156 nassertv(src_channel >= 0 && src_channel <= 3);
157 nassertv(dest_channel >= 0 && dest_channel <= 3);
158 // Make sure that images are valid
159 if (!copy.is_valid() || !is_valid()) {
160 pnmimage_cat.error() << "One of the images is invalid!\n";
161 return;
162 }
163 // Make sure the images are the same size
164 if (_x_size != copy.get_x_size() || _y_size != copy.get_y_size()){
165 pnmimage_cat.error() << "Image size doesn't match!\n";
166 return;
167 }
168
169 // Do the actual copying.
170 if (right_shift >= 0) {
171 xelval dest_mask = ~(src_mask >> right_shift);
172 for (int x = 0; x < _x_size; x++) {
173 for (int y = 0; y < _y_size; y++) {
174 xelval src = copy.get_channel_val(x, y, src_channel);
175 xelval dest = get_channel_val(x, y, dest_channel);
176 dest = (dest & dest_mask) | ((src & src_mask) >> right_shift);
177 set_channel_val(x, y, dest_channel, dest);
178 }
179 }
180 } else {
181 int left_shift = -right_shift;
182 xelval dest_mask = ~(src_mask << left_shift);
183 for (int x = 0; x < _x_size; x++) {
184 for (int y = 0; y < _y_size; y++) {
185 xelval src = copy.get_channel_val(x, y, src_channel);
186 xelval dest = get_channel_val(x, y, dest_channel);
187 dest = (dest & dest_mask) | ((src & src_mask) << left_shift);
188 set_channel_val(x, y, dest_channel, dest);
189 }
190 }
191 }
192}
193
194/**
195 * Copies just the header information into this image. This will blow away
196 * any image data stored in the image. The new image data will be allocated,
197 * but left unitialized.
198 */
199 void PNMImage::
200 copy_header_from(const PNMImageHeader &header) {
201 clear();
202 PNMImageHeader::operator = (header);
203
204 if (_maxval == 0) {
205 _inv_maxval = 0.0f;
206 } else {
207 _inv_maxval = 1.0f / (float)_maxval;
208 }
209
210 if (has_alpha()) {
211 allocate_alpha();
212 }
213
214 allocate_array();
215 setup_encoding();
216 setup_rc();
217}
218
219/**
220 * Move the contents of the other image into this one, and empty the other
221 * image.
222 */
223 void PNMImage::
224 take_from(PNMImage &orig) {
225 clear();
226 PNMImageHeader::operator = (orig);
227 setup_encoding();
228 setup_rc();
229
230 if (has_alpha()) {
231 _alpha = orig._alpha;
232 orig._alpha = nullptr;
233 }
234 _array = orig._array;
235 orig._array = nullptr;
236
237 orig.clear();
238}
239
240/**
241 * Sets the entire image (except the alpha channel) to the given color.
242 */
243 void PNMImage::
244 fill_val(xelval red, xelval green, xelval blue) {
245 if (is_valid()) {
246 for (int y = 0; y < get_y_size(); y++) {
247 for (int x = 0; x < get_x_size(); x++) {
248 set_xel_val(x, y, red, green, blue);
249 }
250 }
251 }
252}
253
254/**
255 * Sets the entire alpha channel to the given level.
256 */
257 void PNMImage::
258 alpha_fill_val(xelval alpha) {
259 if (is_valid()) {
260 if (!has_alpha()) {
261 add_alpha();
262 }
263
264 for (int y = 0; y < get_y_size(); y++) {
265 for (int x = 0; x < get_x_size(); x++) {
266 set_alpha_val(x, y, alpha);
267 }
268 }
269 }
270}
271
272/**
273 * Reads the indicated image filename. If type is non-NULL, it is a
274 * suggestion for the type of file it is. Returns true if successful, false
275 * on error.
276 */
277 bool PNMImage::
278 read(const Filename &filename, PNMFileType *type, bool report_unknown_type) {
279 PNMReader *reader = make_reader(filename, type, report_unknown_type);
280 if (reader == nullptr) {
281 clear();
282 return false;
283 }
284
285 return read(reader);
286}
287
288/**
289 * Reads the image data from the indicated stream.
290 *
291 * The filename is advisory only, and may be used to suggest a type if it has
292 * a known extension.
293 *
294 * If type is non-NULL, it is a suggestion for the type of file it is (and a
295 * non-NULL type will override any magic number test or filename extension
296 * lookup).
297 *
298 * Returns true if successful, false on error.
299 */
300 bool PNMImage::
301 read(std::istream &data, const std::string &filename, PNMFileType *type,
302 bool report_unknown_type) {
303 PNMReader *reader = PNMImageHeader::make_reader
304 (&data, false, filename, std::string(), type, report_unknown_type);
305 if (reader == nullptr) {
306 clear();
307 return false;
308 }
309 return read(reader);
310}
311
312/**
313 * This flavor of read() uses an already-existing PNMReader to read the image
314 * file. You can get a reader via the PNMImageHeader::make_reader() methods.
315 * This is a good way to examine the header of a file (for instance, to
316 * determine its size) before actually reading the entire image.
317 *
318 * The PNMReader is always deleted upon completion, whether successful or not.
319 */
320 bool PNMImage::
321 read(PNMReader *reader) {
322 bool has_read_size = _has_read_size;
323 int read_x_size = _read_x_size;
324 int read_y_size = _read_y_size;
325
326 clear();
327
328 if (reader == nullptr) {
329 return false;
330 }
331
332 if (!reader->is_valid()) {
333 delete reader;
334 return false;
335 }
336
337 if (has_read_size) {
338 reader->set_read_size(read_x_size, read_y_size);
339 }
340 reader->prepare_read();
341
342 copy_header_from(*reader);
343
344 if (reader->is_floating_point()) {
345 // Hmm, it's a floating-point file. Quietly convert it to integer.
346 PfmFile pfm;
347 if (!reader->read_pfm(pfm)) {
348 delete reader;
349 return false;
350 }
351 delete reader;
352 return pfm.store(*this);
353 }
354
355 // We reassign y_size after reading because we might have read a truncated
356 // file.
357 _y_size = reader->read_data(_array, _alpha);
358 delete reader;
359
360 if (_y_size == 0) {
361 clear();
362 return false;
363 }
364
365 setup_encoding();
366 setup_rc();
367
368 if (has_read_size && (_x_size != read_x_size || _y_size != read_y_size)) {
369 // The Reader didn't comply with our size request. Do the sizing
370 // explicitly, then.
371 PNMImage new_image(read_x_size, read_y_size, get_num_channels(),
372 get_maxval(), get_type(), get_color_space());
373 new_image.quick_filter_from(*this);
374 take_from(new_image);
375 }
376
377 return true;
378}
379
380/**
381 * Writes the image to the indicated filename. If type is non-NULL, it is a
382 * suggestion for the type of image file to write.
383 */
384 bool PNMImage::
385 write(const Filename &filename, PNMFileType *type) const {
386 if (!is_valid()) {
387 return false;
388 }
389
390 PNMWriter *writer = PNMImageHeader::make_writer(filename, type);
391 if (writer == nullptr) {
392 return false;
393 }
394
395 return write(writer);
396}
397
398/**
399 * Writes the image to the indicated ostream.
400 *
401 * The filename is advisory only, and may be used suggest a type if it has a
402 * known extension.
403 *
404 * If type is non-NULL, it is a suggestion for the type of image file to
405 * write.
406 */
407 bool PNMImage::
408 write(std::ostream &data, const std::string &filename, PNMFileType *type) const {
409 if (!is_valid()) {
410 return false;
411 }
412
413 PNMWriter *writer = PNMImageHeader::make_writer
414 (&data, false, filename, type);
415 if (writer == nullptr) {
416 return false;
417 }
418
419 return write(writer);
420}
421
422/**
423 * This flavor of write() uses an already-existing PNMWriter to write the
424 * image file. You can get a writer via the PNMImageHeader::make_writer()
425 * methods.
426 *
427 * The PNMWriter is always deleted upon completion, whether successful or not.
428 */
429 bool PNMImage::
430 write(PNMWriter *writer) const {
431 if (writer == nullptr) {
432 return false;
433 }
434
435 if (!is_valid()) {
436 delete writer;
437 return false;
438 }
439
440 writer->copy_header_from(*this);
441
442 if (!writer->supports_integer()) {
443 // Hmm, it's only a floating-point file type. Convert it from the integer
444 // data we have.
445 PfmFile pfm;
446 if (!pfm.load(*this)) {
447 delete writer;
448 return false;
449 }
450 bool success = writer->write_pfm(pfm);
451 delete writer;
452 return success;
453 }
454
455 if (is_grayscale() && !writer->supports_grayscale()) {
456 // Copy the gray values to all channels to help out the writer.
457 for (int y = 0; y < get_y_size(); y++) {
458 for (int x = 0; x<get_x_size(); x++) {
459 ((PNMImage *)this)->set_xel_val(x, y, get_gray_val(x, y));
460 }
461 }
462 }
463
464 int result = writer->write_data(_array, _alpha);
465 delete writer;
466
467 return (result == _y_size);
468}
469
470/**
471 * Translates the image to or from grayscale, color, or four-color mode.
472 * Grayscale images are converted to full-color images with R, G, B set to the
473 * original gray level; color images are converted to grayscale according to
474 * the value of Bright(). The alpha channel, if added, is initialized to
475 * zero.
476 */
477 void PNMImage::
478 set_color_type(PNMImage::ColorType color_type) {
479 nassertv((int)color_type >= 1 && (int)color_type <= 4);
480 if (color_type == get_color_type()) {
481 return;
482 }
483
484 if (!is_grayscale() && is_grayscale(color_type)) {
485 // Convert to grayscale from color
486 for (int y = 0; y < get_y_size(); y++) {
487 for (int x = 0; x < get_x_size(); x++) {
488 set_gray(x, y, get_bright(x, y));
489 }
490 }
491
492 } else if (is_grayscale() && !is_grayscale(color_type)) {
493 // Convert to color from grayscale
494 for (int y = 0; y < get_y_size(); y++) {
495 for (int x = 0; x < get_x_size(); x++) {
496 set_xel_val(x, y, get_gray_val(x, y));
497 }
498 }
499 }
500
501 if (has_alpha() && !has_alpha(color_type)) {
502 // Discard the alpha channel
503 if (_alpha != nullptr) {
504 PANDA_FREE_ARRAY(_alpha);
505 _alpha = nullptr;
506 }
507
508 } else if (!has_alpha() && has_alpha(color_type)) {
509 // Create a new alpha channel
510 allocate_alpha();
511 memset(_alpha, 0, sizeof(xelval) * (_x_size * _y_size));
512 }
513
514 _num_channels = (int)color_type;
515 setup_encoding();
516 setup_rc();
517}
518
519/**
520 * Converts the colors in the image to the indicated color space. This may be
521 * a lossy operation, in particular when going from sRGB to linear. The alpha
522 * channel remains untouched.
523 *
524 * Note that, because functions like get_xel() and set_xel() work on
525 * linearized floating-point values, this conversion won't affect those values
526 * (aside from some minor discrepancies due to storage precision). It does
527 * affect the values used by get_xel_val() and set_xel_val(), though, since
528 * those operate on encoded colors.
529 *
530 * Some color spaces, particularly scRGB, may enforce the use of a particular
531 * maxval setting.
532 */
533 void PNMImage::
534 set_color_space(ColorSpace color_space) {
535 nassertv(color_space != CS_unspecified);
536
537 if (color_space == _color_space) {
538 return;
539 }
540
541 if (_array != nullptr) {
542 size_t array_size = _x_size * _y_size;
543
544 // Note: only convert RGB, since alpha channel is always linear.
545 switch (color_space) {
546 case CS_linear:
547 if (_maxval == 255 && _color_space == CS_sRGB) {
548 for (size_t i = 0; i < array_size; ++i) {
549 xel &col = _array[i];
550 col.r = decode_sRGB_uchar((unsigned char) col.r);
551 col.g = decode_sRGB_uchar((unsigned char) col.g);
552 col.b = decode_sRGB_uchar((unsigned char) col.b);
553 }
554 } else {
555 for (int x = 0; x < _x_size; ++x) {
556 for (int y = 0; y < _y_size; ++y) {
557 LRGBColorf scaled = get_xel(x, y) * _maxval + 0.5f;
558 xel &col = row(y)[x];
559 col.r = clamp_val((int)scaled[0]);
560 col.g = clamp_val((int)scaled[1]);
561 col.b = clamp_val((int)scaled[2]);
562 }
563 }
564 }
565 break;
566
567 case CS_sRGB:
568 if (_maxval == 255 && _color_space == CS_linear) {
569 for (size_t i = 0; i < array_size; ++i) {
570 xel &col = _array[i];
571 col.r = encode_sRGB_uchar((unsigned char) col.r);
572 col.g = encode_sRGB_uchar((unsigned char) col.g);
573 col.b = encode_sRGB_uchar((unsigned char) col.b);
574 }
575 } else {
576 for (int x = 0; x < _x_size; ++x) {
577 for (int y = 0; y < _y_size; ++y) {
578 xel &col = row(y)[x];
579 encode_sRGB_uchar(get_xel_a(x, y), col);
580 }
581 }
582 }
583 break;
584
585 case CS_scRGB:
586 for (int x = 0; x < _x_size; ++x) {
587 for (int y = 0; y < _y_size; ++y) {
588 LRGBColorf scaled = get_xel(x, y) * 8192.f + 4096.5f;
589 xel &col = row(y)[x];
590 col.r = min(max(0, (int)scaled[0]), 65535);
591 col.g = min(max(0, (int)scaled[1]), 65535);
592 col.b = min(max(0, (int)scaled[2]), 65535);
593 }
594 }
595 _maxval = 65535;
596 break;
597
598 default:
599 nassert_raise("invalid color space");
600 return;
601 }
602
603 // Initialize the new encoding settings.
604 _color_space = color_space;
605 setup_encoding();
606 }
607}
608
609/**
610 * Converts the image from RGB to grayscale. Any alpha channel, if present,
611 * is left undisturbed. The optional rc, gc, bc values represent the relative
612 * weights to apply to each channel to convert it to grayscale.
613 */
614 void PNMImage::
615 make_grayscale(float rc, float gc, float bc) {
616 if (is_grayscale()) {
617 return;
618 }
619
620 for (int y = 0; y < get_y_size(); y++) {
621 for (int x = 0; x < get_x_size(); x++) {
622 set_gray(x, y, min(get_bright(x, y, rc, gc, bc), 1.0f));
623 }
624 }
625
626 _num_channels = has_alpha() ? 2 : 1;
627 setup_encoding();
628 setup_rc();
629}
630
631/**
632 * Converts an image in-place to its "premultiplied" form, where, for every
633 * pixel in the image, the red, green, and blue components are multiplied by
634 * that pixel's alpha value.
635 *
636 * This does not modify any alpha values.
637 */
638 void PNMImage::
639 premultiply_alpha() {
640 if (!has_alpha()) {
641 return;
642 }
643
644 for (int y = 0; y < get_y_size(); y++) {
645 for (int x = 0; x < get_x_size(); x++) {
646 float alpha = get_alpha(x, y);
647 float r = get_red(x, y) * alpha;
648 float g = get_green(x, y) * alpha;
649 float b = get_blue(x, y) * alpha;
650 set_xel(x, y, r, g, b);
651 }
652 }
653}
654
655/**
656 * Converts an image in-place to its "straight alpha" form (presumably from a
657 * "premultiplied" form), where, for every pixel in the image, the red, green,
658 * and blue components are divided by that pixel's alpha value.
659 *
660 * This does not modify any alpha values.
661 */
662 void PNMImage::
663 unpremultiply_alpha() {
664 if (!has_alpha()) {
665 return;
666 }
667
668 for (int y = 0; y < get_y_size(); y++) {
669 for (int x = 0; x < get_x_size(); x++) {
670 float alpha = get_alpha(x, y);
671 if (alpha > 0) {
672 float r = get_red(x, y) / alpha;
673 float g = get_green(x, y) / alpha;
674 float b = get_blue(x, y) / alpha;
675 set_xel(x, y, r, g, b);
676 }
677 }
678 }
679}
680
681/**
682 * Performs an in-place reversal of the row (y) data.
683 */
684 void PNMImage::
685 reverse_rows() {
686 if (_array != nullptr) {
687 xel *new_array = (xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xel));
688 for (int y = 0; y < _y_size; y++) {
689 int new_y = _y_size - 1 - y;
690 memcpy(new_array + new_y * _x_size, _array + y * _x_size, _x_size * sizeof(xel));
691 }
692 PANDA_FREE_ARRAY(_array);
693 _array = new_array;
694 }
695
696 if (_alpha != nullptr) {
697 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xelval));
698 for (int y = 0; y < _y_size; y++) {
699 int new_y = _y_size - 1 - y;
700 memcpy(new_alpha + new_y * _x_size, _alpha + y * _x_size, _x_size * sizeof(xelval));
701 }
702 PANDA_FREE_ARRAY(_alpha);
703 _alpha = new_alpha;
704 }
705}
706
707/**
708 * Reverses, transposes, and/or rotates the image in-place according to the
709 * specified parameters. If flip_x is true, the x axis is reversed; if flip_y
710 * is true, the y axis is reversed. Then, if transpose is true, the x and y
711 * axes are exchanged. These parameters can be used to select any combination
712 * of 90-degree or 180-degree rotations and flips.
713 */
714 void PNMImage::
715 flip(bool flip_x, bool flip_y, bool transpose) {
716 if (transpose) {
717 // Transposed case. X becomes Y, Y becomes X.
718 if (_array != nullptr) {
719 xel *new_array = (xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xel));
720 for (int xi = 0; xi < _x_size; ++xi) {
721 xel *row = new_array + xi * _y_size;
722 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
723 for (int yi = 0; yi < _y_size; ++yi) {
724 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
725 xel *source_row = _array + source_yi * _x_size;
726 row[yi] = source_row[source_xi];
727 }
728 }
729 PANDA_FREE_ARRAY(_array);
730 _array = new_array;
731 }
732
733 if (_alpha != nullptr) {
734 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xelval));
735 for (int xi = 0; xi < _x_size; ++xi) {
736 xelval *row = new_alpha + xi * _y_size;
737 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
738 for (int yi = 0; yi < _y_size; ++yi) {
739 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
740 xelval *source_row = _alpha + source_yi * _x_size;
741 row[yi] = source_row[source_xi];
742 }
743 }
744
745 PANDA_FREE_ARRAY(_alpha);
746 _alpha = new_alpha;
747 }
748
749 int t = _x_size;
750 _x_size = _y_size;
751 _y_size = t;
752
753 } else {
754 // Non-transposed. X is X, Y is Y.
755 if (_array != nullptr) {
756 xel *new_array = (xel *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xel));
757 for (int yi = 0; yi < _y_size; ++yi) {
758 xel *row = new_array + yi * _x_size;
759 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
760 xel *source_row = _array + source_yi * _x_size;
761
762 for (int xi = 0; xi < _x_size; ++xi) {
763 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
764 row[xi] = source_row[source_xi];
765 }
766 }
767 PANDA_FREE_ARRAY(_array);
768 _array = new_array;
769 }
770
771 if (_alpha != nullptr) {
772 xelval *new_alpha = (xelval *)PANDA_MALLOC_ARRAY(_x_size * _y_size * sizeof(xelval));
773 for (int yi = 0; yi < _y_size; ++yi) {
774 xelval *row = new_alpha + yi * _x_size;
775 int source_yi = !flip_y ? yi : _y_size - 1 - yi;
776 xelval *source_row = _alpha + source_yi * _x_size;
777
778 for (int xi = 0; xi < _x_size; ++xi) {
779 int source_xi = !flip_x ? xi : _x_size - 1 - xi;
780 row[xi] = source_row[source_xi];
781 }
782 }
783
784 PANDA_FREE_ARRAY(_alpha);
785 _alpha = new_alpha;
786 }
787 }
788}
789
790/**
791 * Rescales the image to the indicated maxval.
792 */
793 void PNMImage::
794 set_maxval(xelval maxval) {
795 nassertv(maxval > 0);
796
797 if (maxval != _maxval) {
798 float ratio = (float)maxval / (float)_maxval;
799
800 if (is_grayscale()) {
801 for (int y = 0; y < get_y_size(); y++) {
802 for (int x = 0; x < get_x_size(); x++) {
803 set_gray_val(x, y, (xelval)((long)get_gray_val(x, y) * ratio));
804 }
805 }
806 } else {
807 for (int y = 0; y < get_y_size(); y++) {
808 for (int x = 0; x < get_x_size(); x++) {
809 set_red_val(x, y, (xelval)((long)get_red_val(x, y) * ratio));
810 set_green_val(x, y, (xelval)((long)get_green_val(x, y) * ratio));
811 set_blue_val(x, y, (xelval)((long)get_blue_val(x, y) * ratio));
812 }
813 }
814 }
815
816 if (has_alpha()) {
817 for (int y = 0; y < get_y_size(); y++) {
818 for (int x = 0; x < get_x_size(); x++) {
819 set_alpha_val(x, y,
820 (xelval)((long)get_alpha_val(x, y) * ratio));
821 }
822 }
823 }
824 _maxval = maxval;
825 setup_encoding();
826 }
827}
828
829/**
830 * Returns the nth component color at the indicated pixel. The channel index
831 * should be in the range 0..(get_num_channels()-1). The channels are ordered
832 * B, G, R, A. This is slightly less optimal than accessing the component
833 * values directly by named methods. The value returned is in the range
834 * 0..maxval.
835 */
836xelval PNMImage::
837 get_channel_val(int x, int y, int channel) const {
838 switch (channel) {
839 case 0:
840 return get_blue_val(x, y);
841
842 case 1:
843 return (_num_channels == 2) ? get_alpha_val(x, y) : get_green_val(x, y);
844
845 case 2:
846 return get_red_val(x, y);
847
848 case 3:
849 return get_alpha_val(x, y);
850
851 default:
852 pnmimage_cat.error()
853 << "Invalid request for channel " << channel << " in "
854 << get_num_channels() << "-channel image.\n";
855 nassert_raise("unexpected channel count");
856 return 0;
857 }
858}
859
860/**
861 * Sets the nth component color at the indicated pixel. The channel index
862 * should be in the range 0..(get_num_channels()-1). The channels are ordered
863 * B, G, R, A. This is slightly less optimal than setting the component
864 * values directly by named methods. The value given should be in the range
865 * 0..maxval.
866 */
867 void PNMImage::
868 set_channel_val(int x, int y, int channel, xelval value) {
869 switch (channel) {
870 case 0:
871 set_blue_val(x, y, value);
872 break;
873
874 case 1:
875 if (_num_channels == 2) {
876 set_alpha_val(x, y, value);
877 } else {
878 set_green_val(x, y, value);
879 }
880 break;
881
882 case 2:
883 set_red_val(x, y, value);
884 break;
885
886 case 3:
887 set_alpha_val(x, y, value);
888 break;
889
890 default:
891 nassert_raise("unexpected channel count");
892 break;
893 }
894}
895
896/**
897 * Returns the nth component color at the indicated pixel. The channel index
898 * should be in the range 0..(get_num_channels()-1). The channels are ordered
899 * B, G, R, A. This is slightly less optimal than accessing the component
900 * values directly by named methods. The value returned is a float in the
901 * range 0..1.
902 */
903 float PNMImage::
904 get_channel(int x, int y, int channel) const {
905 switch (channel) {
906 case 0:
907 return get_blue(x, y);
908
909 case 1:
910 return (_num_channels == 2) ? get_alpha(x, y) : get_green(x, y);
911
912 case 2:
913 return get_red(x, y);
914
915 case 3:
916 return get_alpha(x, y);
917
918 default:
919 pnmimage_cat.error()
920 << "Invalid request for channel " << channel << " in "
921 << get_num_channels() << "-channel image.\n";
922 nassert_raise("unexpected channel count");
923 return 0;
924 }
925}
926
927/**
928 * Sets the nth component color at the indicated pixel. The channel index
929 * should be in the range 0..(get_num_channels()-1). The channels are ordered
930 * B, G, R, A. This is slightly less optimal than setting the component
931 * values directly by named methods. The value given should be a float in the
932 * range 0..1.
933 */
934 void PNMImage::
935 set_channel(int x, int y, int channel, float value) {
936 switch (channel) {
937 case 0:
938 set_blue(x, y, value);
939 break;
940
941 case 1:
942 if (_num_channels == 2) {
943 set_alpha(x, y, value);
944 } else {
945 set_green(x, y, value);
946 }
947 break;
948
949 case 2:
950 set_red(x, y, value);
951 break;
952
953 case 3:
954 set_alpha(x, y, value);
955 break;
956
957 default:
958 nassert_raise("unexpected channel count");
959 break;
960 }
961}
962
963/**
964 * Returns the (r, g, b, a) pixel value at the indicated pixel, using a
965 * PixelSpec object.
966 */
967 PNMImage::PixelSpec PNMImage::
968 get_pixel(int x, int y) const {
969 switch (_num_channels) {
970 case 1:
971 return PixelSpec(get_gray_val(x, y));
972 case 2:
973 return PixelSpec(get_gray_val(x, y), get_alpha_val(x, y));
974 case 3:
975 return PixelSpec(get_xel_val(x, y));
976 case 4:
977 return PixelSpec(get_xel_val(x, y), get_alpha_val(x, y));
978 }
979
980 return PixelSpec(0);
981}
982
983/**
984 * Sets the (r, g, b, a) pixel value at the indicated pixel, using a PixelSpec
985 * object.
986 */
987 void PNMImage::
988 set_pixel(int x, int y, const PixelSpec &pixel) {
989 xel p;
990 PPM_ASSIGN(p, pixel._red, pixel._green, pixel._blue);
991 set_xel_val(x, y, p);
992 if (_alpha != nullptr) {
993 set_alpha_val(x, y, pixel._alpha);
994 }
995}
996
997/**
998 * Smoothly blends the indicated pixel value in with whatever was already in
999 * the image, based on the given alpha value. An alpha of 1.0 is fully opaque
1000 * and completely replaces whatever was there previously; alpha of 0.0 is
1001 * fully transparent and does nothing.
1002 */
1003 void PNMImage::
1004 blend(int x, int y, float r, float g, float b, float alpha) {
1005 if (alpha >= 1.0) {
1006 // Completely replace the previous color.
1007 if (has_alpha()) {
1008 set_alpha(x, y, 1.0);
1009 }
1010 set_xel(x, y, r, g, b);
1011
1012 } else if (alpha > 0.0f) {
1013 // Blend with the previous color.
1014 float prev_alpha = has_alpha() ? get_alpha(x, y) : 1.0f;
1015
1016 if (prev_alpha == 0.0f) {
1017 // Nothing there previously; replace with this new color.
1018 set_alpha(x, y, alpha);
1019 set_xel(x, y, r, g, b);
1020
1021 } else {
1022 // Blend the color with the previous color.
1023 LRGBColorf prev_rgb = get_xel(x, y);
1024 r = r + (1.0f - alpha) * (prev_rgb[0] - r);
1025 g = g + (1.0f - alpha) * (prev_rgb[1] - g);
1026 b = b + (1.0f - alpha) * (prev_rgb[2] - b);
1027 alpha = prev_alpha + alpha * (1.0f - prev_alpha);
1028
1029 if (has_alpha()) {
1030 set_alpha(x, y, alpha);
1031 }
1032 set_xel(x, y, r, g, b);
1033 }
1034 }
1035}
1036
1037/**
1038 * Replaces the underlying PNMImage array with the indicated pointer. Know
1039 * what you are doing! The new array must be the correct size and must have
1040 * been allocated via PANDA_MALLOC_ARRAY(). The PNMImage object becomes the
1041 * owner of this pointer and will eventually free it with PANDA_FREE_ARRAY().
1042 * The previous array, if any, will be freed with PANDA_FREE_ARRAY() when this
1043 * call is made.
1044 */
1045 void PNMImage::
1046 set_array(xel *array) {
1047 if (_array != nullptr) {
1048 PANDA_FREE_ARRAY(_array);
1049 }
1050 _array = array;
1051}
1052
1053/**
1054 * Replaces the underlying PNMImage alpha array with the indicated pointer.
1055 * Know what you are doing! The new array must be the correct size and must
1056 * have been allocated via PANDA_MALLOC_ARRAY(). The PNMImage object becomes
1057 * the owner of this pointer and will eventually free it with
1058 * PANDA_FREE_ARRAY(). The previous array, if any, will be freed with
1059 * PANDA_FREE_ARRAY() when this call is made.
1060 */
1061 void PNMImage::
1062 set_alpha_array(xelval *alpha) {
1063 if (_alpha != nullptr) {
1064 PANDA_FREE_ARRAY(_alpha);
1065 }
1066 _alpha = alpha;
1067}
1068
1069/**
1070 * Copies a rectangular area of another image into a rectangular area of this
1071 * image. Both images must already have been initialized. The upper-left
1072 * corner of the region in both images is specified, and the size of the area;
1073 * if the size is omitted, it defaults to the entire other image, or the
1074 * largest piece that will fit.
1075 */
1076 void PNMImage::
1077 copy_sub_image(const PNMImage &copy, int xto, int yto,
1078 int xfrom, int yfrom, int x_size, int y_size) {
1079 int xmin, ymin, xmax, ymax;
1080 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1081 xmin, ymin, xmax, ymax);
1082
1083 if (get_maxval() == copy.get_maxval() &&
1084 get_color_space() == copy.get_color_space()) {
1085 // The simple case: no pixel value rescaling is required.
1086 int x, y;
1087 for (y = ymin; y < ymax; y++) {
1088 for (x = xmin; x < xmax; x++) {
1089 set_xel_val(x, y, copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom));
1090 }
1091 }
1092
1093 if (has_alpha() && copy.has_alpha()) {
1094 for (y = ymin; y < ymax; y++) {
1095 for (x = xmin; x < xmax; x++) {
1096 set_alpha_val(x, y, copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom));
1097 }
1098 }
1099 }
1100
1101 } else {
1102 // The harder case: rescale pixel values according to maxval.
1103 int x, y;
1104 for (y = ymin; y < ymax; y++) {
1105 for (x = xmin; x < xmax; x++) {
1106 set_xel(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom));
1107 }
1108 }
1109
1110 if (has_alpha() && copy.has_alpha()) {
1111 for (y = ymin; y < ymax; y++) {
1112 for (x = xmin; x < xmax; x++) {
1113 set_alpha(x, y, copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom));
1114 }
1115 }
1116 }
1117 }
1118}
1119
1120/**
1121 * Behaves like copy_sub_image(), except the alpha channel of the copy is used
1122 * to blend the copy into the destination image, instead of overwriting pixels
1123 * unconditionally.
1124 *
1125 * If pixel_scale is not 1.0, it specifies an amount to scale each *alpha*
1126 * value of the source image before applying it to the target image.
1127 *
1128 * If pixel_scale is 1.0 and the copy has no alpha channel, this degenerates
1129 * into copy_sub_image().
1130 */
1131 void PNMImage::
1132 blend_sub_image(const PNMImage &copy, int xto, int yto,
1133 int xfrom, int yfrom, int x_size, int y_size,
1134 float pixel_scale) {
1135 if (!copy.has_alpha() && pixel_scale == 1.0) {
1136 copy_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size);
1137 return;
1138 }
1139
1140 int xmin, ymin, xmax, ymax;
1141 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1142 xmin, ymin, xmax, ymax);
1143
1144 int x, y;
1145 if (copy.has_alpha()) {
1146 for (y = ymin; y < ymax; y++) {
1147 for (x = xmin; x < xmax; x++) {
1148 blend(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom),
1149 copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1150 }
1151 }
1152 } else {
1153 for (y = ymin; y < ymax; y++) {
1154 for (x = xmin; x < xmax; x++) {
1155 blend(x, y, copy.get_xel(x - xmin + xfrom, y - ymin + yfrom),
1156 pixel_scale);
1157 }
1158 }
1159 }
1160}
1161
1162/**
1163 * Behaves like copy_sub_image(), except the copy pixels are added to the
1164 * pixels of the destination, after scaling by the specified pixel_scale.
1165 * Unlike blend_sub_image(), the alpha channel is not treated specially.
1166 */
1167 void PNMImage::
1168 add_sub_image(const PNMImage &copy, int xto, int yto,
1169 int xfrom, int yfrom, int x_size, int y_size,
1170 float pixel_scale) {
1171 int xmin, ymin, xmax, ymax;
1172 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1173 xmin, ymin, xmax, ymax);
1174
1175 int x, y;
1176 if (has_alpha() && copy.has_alpha()) {
1177 for (y = ymin; y < ymax; y++) {
1178 for (x = xmin; x < xmax; x++) {
1179 set_alpha(x, y, get_alpha(x, y) + copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1180 }
1181 }
1182 }
1183
1184 for (y = ymin; y < ymax; y++) {
1185 for (x = xmin; x < xmax; x++) {
1186 LRGBColorf rgb1 = get_xel(x, y);
1187 LRGBColorf rgb2 = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
1188 set_xel(x, y,
1189 rgb1[0] + rgb2[0] * pixel_scale,
1190 rgb1[1] + rgb2[1] * pixel_scale,
1191 rgb1[2] + rgb2[2] * pixel_scale);
1192 }
1193 }
1194}
1195
1196/**
1197 * Behaves like copy_sub_image(), except the copy pixels are multiplied to the
1198 * pixels of the destination, after scaling by the specified pixel_scale.
1199 * Unlike blend_sub_image(), the alpha channel is not treated specially.
1200 */
1201 void PNMImage::
1202 mult_sub_image(const PNMImage &copy, int xto, int yto,
1203 int xfrom, int yfrom, int x_size, int y_size,
1204 float pixel_scale) {
1205 int xmin, ymin, xmax, ymax;
1206 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1207 xmin, ymin, xmax, ymax);
1208
1209 int x, y;
1210 if (has_alpha() && copy.has_alpha()) {
1211 for (y = ymin; y < ymax; y++) {
1212 for (x = xmin; x < xmax; x++) {
1213 set_alpha(x, y, get_alpha(x, y) * copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom) * pixel_scale);
1214 }
1215 }
1216 }
1217
1218 for (y = ymin; y < ymax; y++) {
1219 for (x = xmin; x < xmax; x++) {
1220 LRGBColorf rgb1 = get_xel(x, y);
1221 LRGBColorf rgb2 = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
1222 set_xel(x, y,
1223 rgb1[0] * rgb2[0] * pixel_scale,
1224 rgb1[1] * rgb2[1] * pixel_scale,
1225 rgb1[2] * rgb2[2] * pixel_scale);
1226 }
1227 }
1228}
1229
1230/**
1231 * Behaves like copy_sub_image(), but the resulting color will be the darker
1232 * of the source and destination colors at each pixel (and at each R, G, B, A
1233 * component value).
1234 *
1235 * If pixel_scale is not 1.0, it specifies an amount to scale each pixel value
1236 * of the source image before applying it to the target image. The scale is
1237 * applied with the center at 1.0: scaling the pixel value smaller brings it
1238 * closer to 1.0.
1239 */
1240 void PNMImage::
1241 darken_sub_image(const PNMImage &copy, int xto, int yto,
1242 int xfrom, int yfrom, int x_size, int y_size,
1243 float pixel_scale) {
1244 int xmin, ymin, xmax, ymax;
1245 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1246 xmin, ymin, xmax, ymax);
1247
1248 if (get_maxval() == copy.get_maxval() && pixel_scale == 1.0f &&
1249 get_color_space() == CS_linear && copy.get_color_space() == CS_linear) {
1250 // The simple case: no pixel value rescaling is required.
1251 int x, y;
1252 for (y = ymin; y < ymax; y++) {
1253 for (x = xmin; x < xmax; x++) {
1254 xel c = copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom);
1255 xel o = get_xel_val(x, y);
1256 xel p;
1257 PPM_ASSIGN(p, min(c.r, o.r), min(c.g, o.g), min(c.b, o.b));
1258 set_xel_val(x, y, p);
1259 }
1260 }
1261
1262 if (has_alpha() && copy.has_alpha()) {
1263 for (y = ymin; y < ymax; y++) {
1264 for (x = xmin; x < xmax; x++) {
1265 xelval c = copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
1266 xelval o = get_alpha_val(x, y);
1267 set_alpha_val(x, y, min(c, o));
1268 }
1269 }
1270 }
1271
1272 } else {
1273 // The harder case: rescale pixel values according to maxval.
1274 int x, y;
1275 for (y = ymin; y < ymax; y++) {
1276 for (x = xmin; x < xmax; x++) {
1277 LRGBColorf c = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
1278 LRGBColorf o = get_xel(x, y);
1279 LRGBColorf p;
1280 p.set(min(1.0f - ((1.0f - c[0]) * pixel_scale), o[0]),
1281 min(1.0f - ((1.0f - c[1]) * pixel_scale), o[1]),
1282 min(1.0f - ((1.0f - c[2]) * pixel_scale), o[2]));
1283 set_xel(x, y, p);
1284 }
1285 }
1286
1287 if (has_alpha() && copy.has_alpha()) {
1288 for (y = ymin; y < ymax; y++) {
1289 for (x = xmin; x < xmax; x++) {
1290 float c = copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom);
1291 float o = get_alpha(x, y);
1292 set_alpha(x, y, min(1.0f - ((1.0f - c) * pixel_scale), o));
1293 }
1294 }
1295 }
1296 }
1297}
1298
1299/**
1300 * Behaves like copy_sub_image(), but the resulting color will be the lighter
1301 * of the source and destination colors at each pixel (and at each R, G, B, A
1302 * component value).
1303 *
1304 * If pixel_scale is not 1.0, it specifies an amount to scale each pixel value
1305 * of the source image before applying it to the target image.
1306 */
1307 void PNMImage::
1308 lighten_sub_image(const PNMImage &copy, int xto, int yto,
1309 int xfrom, int yfrom, int x_size, int y_size,
1310 float pixel_scale) {
1311 int xmin, ymin, xmax, ymax;
1312 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1313 xmin, ymin, xmax, ymax);
1314
1315 if (get_maxval() == copy.get_maxval() && pixel_scale == 1.0f &&
1316 get_color_space() == CS_linear && copy.get_color_space() == CS_linear) {
1317 // The simple case: no pixel value rescaling is required.
1318 int x, y;
1319 for (y = ymin; y < ymax; y++) {
1320 for (x = xmin; x < xmax; x++) {
1321 xel c = copy.get_xel_val(x - xmin + xfrom, y - ymin + yfrom);
1322 xel o = get_xel_val(x, y);
1323 xel p;
1324 PPM_ASSIGN(p, max(c.r, o.r), max(c.g, o.g), max(c.b, o.b));
1325 set_xel_val(x, y, p);
1326 }
1327 }
1328
1329 if (has_alpha() && copy.has_alpha()) {
1330 for (y = ymin; y < ymax; y++) {
1331 for (x = xmin; x < xmax; x++) {
1332 xelval c = copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom);
1333 xelval o = get_alpha_val(x, y);
1334 set_alpha_val(x, y, max(c, o));
1335 }
1336 }
1337 }
1338
1339 } else {
1340 // The harder case: rescale pixel values according to maxval.
1341 int x, y;
1342 for (y = ymin; y < ymax; y++) {
1343 for (x = xmin; x < xmax; x++) {
1344 LRGBColorf c = copy.get_xel(x - xmin + xfrom, y - ymin + yfrom);
1345 LRGBColorf o = get_xel(x, y);
1346 LRGBColorf p;
1347 p.set(max(c[0] * pixel_scale, o[0]),
1348 max(c[1] * pixel_scale, o[1]),
1349 max(c[2] * pixel_scale, o[2]));
1350 set_xel(x, y, p);
1351 }
1352 }
1353
1354 if (has_alpha() && copy.has_alpha()) {
1355 for (y = ymin; y < ymax; y++) {
1356 for (x = xmin; x < xmax; x++) {
1357 float c = copy.get_alpha(x - xmin + xfrom, y - ymin + yfrom);
1358 float o = get_alpha(x, y);
1359 set_alpha(x, y, max(c * pixel_scale, o));
1360 }
1361 }
1362 }
1363 }
1364}
1365
1366/**
1367 * Selectively copies each pixel from either one source or another source,
1368 * depending on the pixel value of the indicated channel of select_image.
1369 *
1370 * For each pixel (x, y):
1371 *
1372 * s = select_image.get_channel(x, y, channel). Set this image's (x, y) to:
1373 *
1374 * lt.get_xel(x, y) if s < threshold, or
1375 *
1376 * ge.get_xel(x, y) if s >= threshold
1377 *
1378 * Any of select_image, lt, or ge may be the same PNMImge object as this
1379 * image, or the same as each other; or they may all be different. All images
1380 * must be the same size. As a special case, lt and ge may both be 1x1 images
1381 * instead of the source image size.
1382 */
1383 void PNMImage::
1384 threshold(const PNMImage &select_image, int channel, float threshold,
1385 const PNMImage &lt, const PNMImage &ge) {
1386 nassertv(get_x_size() <= select_image.get_x_size() && get_y_size() <= select_image.get_y_size());
1387 nassertv(channel >= 0 && channel < select_image.get_num_channels());
1388
1389 xelval threshold_val = select_image.to_val(threshold);
1390
1391 if (lt.get_x_size() == 1 && lt.get_y_size() == 1 &&
1392 ge.get_x_size() == 1 && ge.get_y_size() == 1) {
1393 // FIXME: what if select_image has different color space? 1x1 source
1394 // images.
1395 xel lt_val = lt.get_xel_val(0, 0);
1396 xelval lt_alpha = 0;
1397 if (lt.has_alpha()) {
1398 lt_alpha = lt.get_alpha_val(0, 0);
1399 }
1400 if (lt.get_maxval() != get_maxval()) {
1401 float scale = (float)get_maxval() / (float)lt.get_maxval();
1402 PPM_ASSIGN(lt_val,
1403 (xelval)(PPM_GETR(lt_val) * scale + 0.5),
1404 (xelval)(PPM_GETG(lt_val) * scale + 0.5),
1405 (xelval)(PPM_GETB(lt_val) * scale + 0.5));
1406 lt_alpha = (xelval)(lt_alpha * scale + 0.5);
1407 }
1408
1409 xel ge_val = ge.get_xel_val(0, 0);
1410 xelval ge_alpha = 0;
1411 if (ge.has_alpha()) {
1412 ge_alpha = ge.get_alpha_val(0, 0);
1413 }
1414 if (ge.get_maxval() != get_maxval()) {
1415 float scale = (float)get_maxval() / (float)ge.get_maxval();
1416 PPM_ASSIGN(ge_val,
1417 (xelval)(PPM_GETR(ge_val) * scale + 0.5),
1418 (xelval)(PPM_GETG(ge_val) * scale + 0.5),
1419 (xelval)(PPM_GETB(ge_val) * scale + 0.5));
1420 ge_alpha = (xelval)(ge_alpha * scale + 0.5);
1421 }
1422
1423 int x, y;
1424
1425 if (channel == 3) {
1426 // Further special case: the alpha channel.
1427 if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
1428 // Copy alpha channel too.
1429 for (y = 0; y < get_y_size(); y++) {
1430 for (x = 0; x < get_x_size(); x++) {
1431 if (select_image.get_alpha_val(x, y) < threshold_val) {
1432 set_xel_val(x, y, lt_val);
1433 set_alpha_val(x, y, lt_alpha);
1434 } else {
1435 set_xel_val(x, y, ge_val);
1436 set_alpha_val(x, y, ge_alpha);
1437 }
1438 }
1439 }
1440
1441 } else {
1442 // Don't copy alpha channel.
1443 for (y = 0; y < get_y_size(); y++) {
1444 for (x = 0; x < get_x_size(); x++) {
1445 if (select_image.get_alpha_val(x, y) < threshold_val) {
1446 set_xel_val(x, y, lt_val);
1447 } else {
1448 set_xel_val(x, y, ge_val);
1449 }
1450 }
1451 }
1452 }
1453 } else {
1454 // Any generic channel.
1455 if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
1456 // Copy alpha channel too.
1457 for (y = 0; y < get_y_size(); y++) {
1458 for (x = 0; x < get_x_size(); x++) {
1459 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1460 set_xel_val(x, y, lt_val);
1461 set_alpha_val(x, y, lt_alpha);
1462 } else {
1463 set_xel_val(x, y, ge_val);
1464 set_alpha_val(x, y, ge_alpha);
1465 }
1466 }
1467 }
1468
1469 } else {
1470 // Don't copy alpha channel.
1471 for (y = 0; y < get_y_size(); y++) {
1472 for (x = 0; x < get_x_size(); x++) {
1473 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1474 set_xel_val(x, y, lt_val);
1475 } else {
1476 set_xel_val(x, y, ge_val);
1477 }
1478 }
1479 }
1480 }
1481 }
1482
1483 } else {
1484 // Same-sized source images.
1485 nassertv(get_x_size() <= lt.get_x_size() && get_y_size() <= lt.get_y_size());
1486 nassertv(get_x_size() <= ge.get_x_size() && get_y_size() <= ge.get_y_size());
1487
1488 if (get_maxval() == lt.get_maxval() && get_maxval() == ge.get_maxval() &&
1489 get_color_space() == lt.get_color_space() &&
1490 get_color_space() == ge.get_color_space()) {
1491 // Simple case: the maxvals are all equal. Copy by integer value.
1492 int x, y;
1493
1494 if (channel == 3) {
1495 // Further special case: the alpha channel.
1496 if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
1497 // Copy alpha channel too.
1498 for (y = 0; y < get_y_size(); y++) {
1499 for (x = 0; x < get_x_size(); x++) {
1500 if (select_image.get_alpha_val(x, y) < threshold_val) {
1501 set_xel_val(x, y, lt.get_xel_val(x, y));
1502 set_alpha_val(x, y, lt.get_alpha_val(x, y));
1503 } else {
1504 set_xel_val(x, y, ge.get_xel_val(x, y));
1505 set_alpha_val(x, y, ge.get_alpha_val(x, y));
1506 }
1507 }
1508 }
1509
1510 } else {
1511 // Don't copy alpha channel.
1512 for (y = 0; y < get_y_size(); y++) {
1513 for (x = 0; x < get_x_size(); x++) {
1514 if (select_image.get_alpha_val(x, y) < threshold_val) {
1515 set_xel_val(x, y, lt.get_xel_val(x, y));
1516 } else {
1517 set_xel_val(x, y, ge.get_xel_val(x, y));
1518 }
1519 }
1520 }
1521 }
1522 } else {
1523 // Any generic channel.
1524 if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
1525 // Copy alpha channel too.
1526 for (y = 0; y < get_y_size(); y++) {
1527 for (x = 0; x < get_x_size(); x++) {
1528 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1529 set_xel_val(x, y, lt.get_xel_val(x, y));
1530 set_alpha_val(x, y, lt.get_alpha_val(x, y));
1531 } else {
1532 set_xel_val(x, y, ge.get_xel_val(x, y));
1533 set_alpha_val(x, y, ge.get_alpha_val(x, y));
1534 }
1535 }
1536 }
1537
1538 } else {
1539 // Don't copy alpha channel.
1540 for (y = 0; y < get_y_size(); y++) {
1541 for (x = 0; x < get_x_size(); x++) {
1542 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1543 set_xel_val(x, y, lt.get_xel_val(x, y));
1544 } else {
1545 set_xel_val(x, y, ge.get_xel_val(x, y));
1546 }
1547 }
1548 }
1549 }
1550 }
1551
1552 } else {
1553 // General case: the maxvals are different. Copy by floating-point
1554 // value.
1555 int x, y;
1556
1557 if (has_alpha() && lt.has_alpha() && ge.has_alpha()) {
1558 for (y = 0; y < get_y_size(); y++) {
1559 for (x = 0; x < get_x_size(); x++) {
1560 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1561 set_xel(x, y, lt.get_xel(x, y));
1562 set_alpha(x, y, lt.get_alpha(x, y));
1563 } else {
1564 set_xel(x, y, ge.get_xel(x, y));
1565 set_alpha(x, y, ge.get_alpha(x, y));
1566 }
1567 }
1568 }
1569 } else {
1570 for (y = 0; y < get_y_size(); y++) {
1571 for (x = 0; x < get_x_size(); x++) {
1572 if (select_image.get_channel_val(x, y, channel) < threshold_val) {
1573 set_xel(x, y, lt.get_xel(x, y));
1574 } else {
1575 set_xel(x, y, ge.get_xel(x, y));
1576 }
1577 }
1578 }
1579 }
1580 }
1581 }
1582}
1583
1584/**
1585 * Replaces this image with a grayscale image whose gray channel represents
1586 * the linear Manhattan distance from the nearest dark pixel in the given mask
1587 * image, up to the specified radius value (which also becomes the new
1588 * maxval). radius may range from 0 to maxmaxval; smaller values will compute
1589 * faster. A dark pixel is defined as one whose pixel value is < threshold.
1590 *
1591 * If shrink_from_border is true, then the mask image is considered to be
1592 * surrounded by a border of dark pixels; otherwise, the border isn't
1593 * considered.
1594 *
1595 * This can be used, in conjunction with threshold, to shrink a mask image
1596 * inwards by a certain number of pixels.
1597 *
1598 * The mask image may be the same image as this one, in which case it is
1599 * destructively modified by this process.
1600 */
1601 void PNMImage::
1602 fill_distance_inside(const PNMImage &mask, float threshold, int radius, bool shrink_from_border) {
1603 nassertv(radius <= PNM_MAXMAXVAL);
1604 PNMImage dist(mask.get_x_size(), mask.get_y_size(), 1, radius, nullptr, CS_linear);
1605 dist.fill_val(radius);
1606
1607 xelval threshold_val = mask.to_val(threshold);
1608
1609 for (int yi = 0; yi < mask.get_y_size(); ++yi) {
1610 for (int xi = 0; xi < mask.get_x_size(); ++xi) {
1611 if (mask.get_gray_val(xi, yi) < threshold_val) {
1612 dist.do_fill_distance(xi, yi, 0);
1613 }
1614 }
1615 }
1616
1617 if (shrink_from_border) {
1618 // Also measure from the image border.
1619 for (int yi = 0; yi < mask.get_y_size(); ++yi) {
1620 dist.do_fill_distance(0, yi, 1);
1621 dist.do_fill_distance(mask.get_x_size() - 1, yi, 1);
1622 }
1623 for (int xi = 0; xi < mask.get_x_size(); ++xi) {
1624 dist.do_fill_distance(xi, 0, 1);
1625 dist.do_fill_distance(xi, mask.get_y_size() - 1, 1);
1626 }
1627 }
1628
1629 take_from(dist);
1630}
1631
1632/**
1633 * Replaces this image with a grayscale image whose gray channel represents
1634 * the linear Manhattan distance from the nearest white pixel in the given
1635 * mask image, up to the specified radius value (which also becomes the new
1636 * maxval). radius may range from 0 to maxmaxval; smaller values will compute
1637 * faster. A white pixel is defined as one whose pixel value is >= threshold.
1638 *
1639 * This can be used, in conjunction with threshold, to grow a mask image
1640 * outwards by a certain number of pixels.
1641 *
1642 * The mask image may be the same image as this one, in which case it is
1643 * destructively modified by this process.
1644 */
1645 void PNMImage::
1646 fill_distance_outside(const PNMImage &mask, float threshold, int radius) {
1647 nassertv(radius <= PNM_MAXMAXVAL);
1648 PNMImage dist(mask.get_x_size(), mask.get_y_size(), 1, radius, nullptr, CS_linear);
1649 dist.fill_val(radius);
1650
1651 xelval threshold_val = mask.to_val(threshold);
1652
1653 for (int yi = 0; yi < mask.get_y_size(); ++yi) {
1654 for (int xi = 0; xi < mask.get_x_size(); ++xi) {
1655 if (mask.get_gray_val(xi, yi) >= threshold_val) {
1656 dist.do_fill_distance(xi, yi, 0);
1657 }
1658 }
1659 }
1660
1661 take_from(dist);
1662}
1663
1664/**
1665 * index_image is a WxH grayscale image, while pixel_values is an Nx1 color
1666 * (or grayscale) image. Typically pixel_values will be a 256x1 image.
1667 *
1668 * Fills the PNMImage with a new image the same width and height as
1669 * index_image, with the same number of channels as pixel_values.
1670 *
1671 * Each pixel of the new image is computed with the formula:
1672 *
1673 * new_image(x, y) = pixel_values(index_image(x, y)[channel], 0)
1674 *
1675 * At present, no interpolation is performed; the nearest value in
1676 * pixel_values is discovered. This may change in the future.
1677 */
1678 void PNMImage::
1679 indirect_1d_lookup(const PNMImage &index_image, int channel,
1680 const PNMImage &pixel_values) {
1681 clear(index_image.get_x_size(), index_image.get_y_size(),
1682 pixel_values.get_num_channels(), pixel_values.get_maxval());
1683
1684 for (int yi = 0; yi < get_y_size(); ++yi) {
1685 for (int xi = 0; xi < get_x_size(); ++xi) {
1686 int v = int(index_image.get_channel(xi, yi, channel) * (pixel_values.get_x_size() - 1) + 0.5);
1687 nassertv(v >= 0 && v < pixel_values.get_x_size());
1688 set_xel_val(xi, yi, pixel_values.get_xel_val(v, 0));
1689 if (has_alpha()) {
1690 set_alpha_val(xi, yi, pixel_values.get_alpha_val(v, 0));
1691 }
1692 }
1693 }
1694}
1695
1696/**
1697 * Rescales the RGB channel values so that any values in the original image
1698 * between min_val and max_val are expanded to the range 0 .. 1. Values below
1699 * min_val are set to 0, and values above max_val are set to 1. Does not
1700 * affect the alpha channel, if any.
1701 */
1702 void PNMImage::
1703 rescale(float min_val, float max_val) {
1704 float scale = max_val - min_val;
1705
1706 if (_num_channels <= 2) {
1707 // Grayscale.
1708 for (int y = 0; y < get_y_size(); y++) {
1709 for (int x = 0; x < get_x_size(); x++) {
1710 float val = get_gray(x, y);
1711 set_gray(x, y, (val - min_val) / scale);
1712 }
1713 }
1714 } else {
1715 // RGB(A).
1716 for (int y = 0; y < get_y_size(); y++) {
1717 for (int x = 0; x < get_x_size(); x++) {
1718 LRGBColorf xel = get_xel(x, y);
1719 set_xel(x, y,
1720 (xel[0] - min_val) / scale,
1721 (xel[1] - min_val) / scale,
1722 (xel[2] - min_val) / scale);
1723 }
1724 }
1725 }
1726}
1727
1728/**
1729 * Copies just a single channel from the source image into a single channel of
1730 * this image, leaving the remaining channels alone.
1731 */
1732 void PNMImage::
1733 copy_channel(const PNMImage &copy, int xto, int yto, int cto,
1734 int xfrom, int yfrom, int cfrom,
1735 int x_size, int y_size) {
1736 int xmin, ymin, xmax, ymax;
1737 setup_sub_image(copy, xto, yto, xfrom, yfrom, x_size, y_size,
1738 xmin, ymin, xmax, ymax);
1739
1740 if (get_maxval() == copy.get_maxval() &&
1741 get_color_space() == copy.get_color_space()) {
1742 // The simple case: no pixel value rescaling is required.
1743 int x, y;
1744 for (y = ymin; y < ymax; y++) {
1745 if (cto == 3 && cfrom == 3) {
1746 // To alpha channel, from alpha channel.
1747 for (x = xmin; x < xmax; x++) {
1748 set_alpha_val(x, y, copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom));
1749 }
1750 } else if (cto == 3 && cfrom == 0) {
1751 // To alpha channel, from blue (or gray) channel.
1752 for (x = xmin; x < xmax; x++) {
1753 set_alpha_val(x, y, copy.get_blue_val(x - xmin + xfrom, y - ymin + yfrom));
1754 }
1755 } else if (cto == 0 && cfrom == 3) {
1756 // To blue (or gray) channel, from alpha channel.
1757 for (x = xmin; x < xmax; x++) {
1758 set_blue_val(x, y, copy.get_alpha_val(x - xmin + xfrom, y - ymin + yfrom));
1759 }
1760 } else {
1761 // Generic channels.
1762 for (x = xmin; x < xmax; x++) {
1763 set_channel_val(x, y, cto, copy.get_channel_val(x - xmin + xfrom, y - ymin + yfrom, cfrom));
1764 }
1765 }
1766 }
1767
1768 } else {
1769 // The harder case: rescale pixel values according to maxval.
1770 int x, y;
1771 for (y = ymin; y < ymax; y++) {
1772 for (x = xmin; x < xmax; x++) {
1773 set_channel(x, y, cto, copy.get_channel(x - xmin + xfrom, y - ymin + yfrom, cfrom));
1774 }
1775 }
1776 }
1777}
1778
1779/**
1780 * Renders a solid-color circle, with a fuzzy edge, into the center of the
1781 * PNMImage. If the PNMImage is non-square, this actually renders an ellipse.
1782 *
1783 * The min_radius and max_radius are in the scale 0..1, where 1.0 means the
1784 * full width of the image. If min_radius == max_radius, the edge is sharp
1785 * (but still antialiased); otherwise, the pixels between min_radius and
1786 * max_radius are smoothly blended between fg and bg colors.
1787 */
1788 void PNMImage::
1789 render_spot(const LColorf &fg, const LColorf &bg,
1790 float min_radius, float max_radius) {
1791 if (_x_size == 0 || _y_size == 0) {
1792 return;
1793 }
1794
1795 float x_scale = 2.0 / _x_size;
1796 float y_scale = 2.0 / _y_size;
1797
1798 // If the width is even, x_center1 == x_center0. If the width is odd,
1799 // x_center1 == x_center0 + 1.
1800 int x_center0 = _x_size / 2;
1801 int y_center0 = _y_size / 2;
1802 int x_center1 = (_x_size + 1) / 2;
1803 int y_center1 = (_y_size + 1) / 2;
1804
1805 float min_r2 = min_radius * min_radius;
1806 float max_r2 = max_radius * max_radius;
1807
1808 for (int yi = 0; yi < y_center1; ++yi) {
1809 float y = yi * y_scale;
1810 float y2_inner = y * y;
1811 float y2_outer = (y + y_scale) * (y + y_scale);
1812 for (int xi = 0; xi < x_center1; ++xi) {
1813 float x = xi * x_scale;
1814 float d2_inner = (x * x + y2_inner);
1815 float d2_outer = ((x + x_scale) * (x + x_scale) + y2_outer);
1816 float d2_a = ((x + x_scale) * (x + x_scale) + y2_inner);
1817 float d2_b = (x * x + y2_outer);
1818
1819 if ((d2_inner <= min_r2) &&
1820 (d2_outer <= min_r2) &&
1821 (d2_a <= min_r2) &&
1822 (d2_b <= min_r2)) {
1823 // This pixel is solidly in the center of the spot.
1824 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, fg);
1825 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, fg);
1826 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, fg);
1827 set_xel_a(x_center0 + xi, y_center0 + yi, fg);
1828
1829 } else if ((d2_inner > max_r2) &&
1830 (d2_outer > max_r2) &&
1831 (d2_a > max_r2) &&
1832 (d2_b > max_r2)) {
1833 // This pixel is solidly outside the spot.
1834 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, bg);
1835 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, bg);
1836 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, bg);
1837 set_xel_a(x_center0 + xi, y_center0 + yi, bg);
1838
1839 } else {
1840 // This pixel is in a feathered area or along the antialiased edge.
1841 LColorf c_outer, c_inner, c_a, c_b;
1842 compute_spot_pixel(c_outer, d2_outer, min_radius, max_radius, fg, bg);
1843 compute_spot_pixel(c_inner, d2_inner, min_radius, max_radius, fg, bg);
1844 compute_spot_pixel(c_a, d2_a, min_radius, max_radius, fg, bg);
1845 compute_spot_pixel(c_b, d2_b, min_radius, max_radius, fg, bg);
1846
1847 // Now average all four pixels for the antialiased result.
1848 LColorf c;
1849 c = (c_outer + c_inner + c_a + c_b) * 0.25;
1850
1851 set_xel_a(x_center1 - 1 - xi, y_center1 - 1 - yi, c);
1852 set_xel_a(x_center0 + xi, y_center1 - 1 - yi, c);
1853 set_xel_a(x_center1 - 1 - xi, y_center0 + yi, c);
1854 set_xel_a(x_center0 + xi, y_center0 + yi, c);
1855 }
1856 }
1857 }
1858}
1859
1860/**
1861 * Expands the image by the indicated number of pixels on each edge. The new
1862 * pixels are set to the indicated color.
1863 *
1864 * If any of the values is negative, this actually crops the image.
1865 */
1866 void PNMImage::
1867 expand_border(int left, int right, int bottom, int top,
1868 const LColorf &color) {
1869 PNMImage new_image(get_x_size() + left + right,
1870 get_y_size() + bottom + top,
1871 get_num_channels(), get_maxval(),
1872 get_type(), get_color_space());
1873 new_image.fill(color[0], color[1], color[2]);
1874 if (has_alpha()) {
1875 new_image.alpha_fill(color[3]);
1876 }
1877 new_image.copy_sub_image(*this, left, top);
1878
1879 take_from(new_image);
1880}
1881
1882/**
1883 * Resizes from the indicated image into this one by performing a nearest-
1884 * point sample.
1885 */
1886 void PNMImage::
1887 unfiltered_stretch_from(const PNMImage &copy) {
1888 for (int yt = 0; yt < get_y_size(); yt++) {
1889 int ys = yt * copy.get_y_size() / get_y_size();
1890 for (int xt = 0; xt < get_x_size(); xt++) {
1891 int xs = xt * copy.get_x_size() / get_x_size();
1892 set_xel(xt, yt, copy.get_xel(xs, ys));
1893 }
1894 }
1895
1896 if (has_alpha() && copy.has_alpha()) {
1897 for (int yt = 0; yt < get_y_size(); yt++) {
1898 int ys = yt * copy.get_y_size() / get_y_size();
1899 for (int xt = 0; xt < get_x_size(); xt++) {
1900 int xs = xt * copy.get_x_size() / get_x_size();
1901 set_alpha(xt, yt, copy.get_alpha(xs, ys));
1902 }
1903 }
1904 }
1905}
1906
1907/**
1908 * Computes a histogram of the colors used in the image.
1909 */
1910 void PNMImage::
1911 make_histogram(PNMImage::Histogram &histogram) {
1912 HistMap hist_map;
1913 PixelCount pixels;
1914
1915 int num_pixels = _x_size * _y_size;
1916
1917 compute_histogram(hist_map, _array, _alpha);
1918
1919 pixels.reserve(hist_map.size());
1920 HistMap::const_iterator hi;
1921 for (hi = hist_map.begin(); hi != hist_map.end(); ++hi) {
1922 if ((*hi).second <= num_pixels) {
1923 pixels.push_back(PixelSpecCount((*hi).first, (*hi).second));
1924 }
1925 }
1926 std::sort(pixels.begin(), pixels.end());
1927
1928 histogram.swap(pixels, hist_map);
1929}
1930
1931/**
1932 * Reduces the number of unique colors in the image to (at most) the given
1933 * count. Fewer colors than requested may be left in the image after this
1934 * operation, but never more.
1935 *
1936 * At present, this is only supported on images without an alpha channel.
1937 *
1938 * @since 1.10.5
1939 */
1940 void PNMImage::
1941 quantize(size_t max_colors) {
1942 nassertv(_array != nullptr);
1943 nassertv(!has_alpha());
1944 size_t array_size = _x_size * _y_size;
1945
1946 // Get all the unique colors in this image.
1947 pmap<xel, xel> color_map;
1948 for (size_t i = 0; i < array_size; ++i) {
1949 color_map[_array[i]];
1950 }
1951
1952 size_t num_colors = color_map.size();
1953 if (num_colors <= max_colors) {
1954 // We are already down to the requested number of colors.
1955 return;
1956 }
1957
1958 // Collect all the colors into a contiguous array.
1959 xel *colors = (xel *)alloca(num_colors * sizeof(xel));
1960 size_t i = 0;
1961 for (pmap<xel, xel>::const_iterator it = color_map.begin();
1962 it != color_map.end(); ++it) {
1963 colors[i++] = it->first;
1964 }
1965 nassertv(i == num_colors);
1966
1967 // Apply the median cut algorithm, which will give us a color map.
1968 r_quantize(color_map, max_colors, colors, num_colors);
1969
1970 // Replace all the existing colors with the corresponding bucket average.
1971 for (size_t i = 0; i < array_size; ++i) {
1972 _array[i] = color_map[_array[i]];
1973 }
1974}
1975
1976/**
1977 * Fills the image with a grayscale perlin noise pattern based on the
1978 * indicated parameters. Uses set_xel to set the grayscale values. The sx
1979 * and sy parameters are in multiples of the size of this image. See also the
1980 * PerlinNoise2 class in mathutil.
1981 */
1982 void PNMImage::
1983 perlin_noise_fill(float sx, float sy, int table_size, unsigned long seed) {
1984 float x, y;
1985 float noise;
1986 PerlinNoise2 perlin (sx * _x_size, sy * _y_size, table_size, seed);
1987 for (x = 0; x < _x_size; ++x) {
1988 for (y = 0; y < _y_size; ++y) {
1989 noise = perlin.noise(x, y);
1990 set_xel(x, y, 0.5 * (noise + 1.0));
1991 }
1992 }
1993}
1994
1995/**
1996 * Variant of perlin_noise_fill that uses an existing StackedPerlinNoise2
1997 * object.
1998 */
1999 void PNMImage::
2000 perlin_noise_fill(StackedPerlinNoise2 &perlin) {
2001 float x, y;
2002 float noise;
2003 for (x = 0; x < _x_size; ++x) {
2004 for (y = 0; y < _y_size; ++y) {
2005 noise = perlin.noise(x / (float) _x_size, y / (float) _y_size);
2006 set_xel(x, y, 0.5 * (noise + 1.0));
2007 }
2008 }
2009}
2010
2011/**
2012 * Transforms every pixel using the operation (Ro,Go,Bo) =
2013 * conv.xform_point(Ri,Gi,Bi); Input must be a color image.
2014 */
2015 void PNMImage::
2016 remix_channels(const LMatrix4 &conv) {
2017 int nchannels = get_num_channels();
2018 nassertv((nchannels >= 3) && (nchannels <= 4));
2019 for (int y = 0; y < get_y_size(); y++) {
2020 for (int x = 0; x < get_x_size(); x++) {
2021 LVector3 inv(get_red(x,y), get_green(x,y), get_blue(x,y));
2022 LVector3 outv(conv.xform_point(inv));
2023 set_xel(x, y, outv[0], outv[1], outv[2]);
2024 }
2025 }
2026}
2027
2028/**
2029 * Adjusts each channel of the image by raising the corresponding component
2030 * value to the indicated exponent, such that L' = L ^ exponent. For a
2031 * grayscale image, the blue_exponent value is used for the grayscale value,
2032 * and red_exponent and green_exponent are unused.
2033 */
2034 void PNMImage::
2035 apply_exponent(float red_exponent, float green_exponent, float blue_exponent,
2036 float alpha_exponent) {
2037 int num_channels = _num_channels;
2038 if (has_alpha() && alpha_exponent == 1.0f) {
2039 // If the alpha_exponent is 1, don't bother to apply it.
2040 --num_channels;
2041 }
2042
2043 int x, y;
2044
2045 if (red_exponent == 1.0f && green_exponent == 1.0f && blue_exponent == 1.0f) {
2046 // If the RGB components are all 1, apply only to the alpha channel.
2047 switch (num_channels) {
2048 case 1:
2049 case 3:
2050 break;
2051
2052 case 2:
2053 case 4:
2054 for (y = 0; y < _y_size; ++y) {
2055 for (x = 0; x < _x_size; ++x) {
2056 float alpha = get_alpha(x, y);
2057 alpha = cpow(alpha, blue_exponent);
2058 set_alpha(x, y, alpha);
2059 }
2060 }
2061 break;
2062 }
2063
2064 } else {
2065 // Apply to the color andor alpha channels.
2066
2067 switch (num_channels) {
2068 case 1:
2069 for (y = 0; y < _y_size; ++y) {
2070 for (x = 0; x < _x_size; ++x) {
2071 float gray = get_gray(x, y);
2072 gray = cpow(gray, blue_exponent);
2073 set_gray(x, y, gray);
2074 }
2075 }
2076 break;
2077
2078 case 2:
2079 for (y = 0; y < _y_size; ++y) {
2080 for (x = 0; x < _x_size; ++x) {
2081 float gray = get_gray(x, y);
2082 gray = cpow(gray, blue_exponent);
2083 set_gray(x, y, gray);
2084
2085 float alpha = get_alpha(x, y);
2086 alpha = cpow(alpha, blue_exponent);
2087 set_alpha(x, y, alpha);
2088 }
2089 }
2090 break;
2091
2092 case 3:
2093 for (y = 0; y < _y_size; ++y) {
2094 for (x = 0; x < _x_size; ++x) {
2095 LRGBColorf color = get_xel(x, y);
2096 color[0] = cpow(color[0], red_exponent);
2097 color[1] = cpow(color[1], green_exponent);
2098 color[2] = cpow(color[2], blue_exponent);
2099 set_xel(x, y, color);
2100 }
2101 }
2102 break;
2103
2104 case 4:
2105 for (y = 0; y < _y_size; ++y) {
2106 for (x = 0; x < _x_size; ++x) {
2107 LColorf color = get_xel_a(x, y);
2108 color[0] = cpow(color[0], red_exponent);
2109 color[1] = cpow(color[1], green_exponent);
2110 color[2] = cpow(color[2], blue_exponent);
2111 color[3] = cpow(color[3], alpha_exponent);
2112 set_xel_a(x, y, color);
2113 }
2114 }
2115 break;
2116 }
2117 }
2118}
2119
2120/**
2121 * Sets the _default_rc,bc,gc values appropriately according to the color type
2122 * of the image, so that get_bright() will return a meaningful value for both
2123 * color and grayscale images.
2124 */
2125 void PNMImage::
2126setup_rc() {
2127 if (is_grayscale()) {
2128 _default_rc = 0.0;
2129 _default_gc = 0.0;
2130 _default_bc = 1.0;
2131 } else {
2132 _default_rc = lumin_red;
2133 _default_gc = lumin_grn;
2134 _default_bc = lumin_blu;
2135 }
2136}
2137
2138/**
2139 * Sets the _xel_encoding value apppropriately according to the color space,
2140 * maxval and whether the image has an alpha channel, so that to_val and
2141 * from_val will work correctly (and possibly more efficiently). Should be
2142 * called after any call to set_maxval.
2143 */
2144 void PNMImage::
2145setup_encoding() {
2146 if (_maxval == 0) {
2147 _inv_maxval = 0.0f;
2148 } else {
2149 _inv_maxval = 1.0f / (float)_maxval;
2150 }
2151
2152 if (has_alpha()) {
2153 switch (_color_space) {
2154 case CS_linear:
2155 _xel_encoding = XE_generic_alpha;
2156 break;
2157
2158 case CS_sRGB:
2159 if (get_maxval() == 255) {
2160 if (has_sse2_sRGB_encode()) {
2161 _xel_encoding = XE_uchar_sRGB_alpha_sse2;
2162 } else {
2163 _xel_encoding = XE_uchar_sRGB_alpha;
2164 }
2165 } else {
2166 _xel_encoding = XE_generic_sRGB_alpha;
2167 }
2168 break;
2169
2170 case CS_scRGB:
2171 _xel_encoding = XE_scRGB_alpha;
2172 nassertv(get_maxval() == 65535);
2173 break;
2174
2175 default:
2176 nassert_raise("invalid color space");
2177 break;
2178 }
2179 } else {
2180 switch (_color_space) {
2181 case CS_linear:
2182 _xel_encoding = XE_generic;
2183 break;
2184
2185 case CS_sRGB:
2186 if (get_maxval() == 255) {
2187 if (has_sse2_sRGB_encode()) {
2188 _xel_encoding = XE_uchar_sRGB_sse2;
2189 } else {
2190 _xel_encoding = XE_uchar_sRGB;
2191 }
2192 } else {
2193 _xel_encoding = XE_generic_sRGB;
2194 }
2195 break;
2196
2197 case CS_scRGB:
2198 _xel_encoding = XE_scRGB;
2199 nassertv(get_maxval() == 65535);
2200 break;
2201
2202 default:
2203 nassert_raise("invalid color space");
2204 break;
2205 }
2206 }
2207}
2208
2209/**
2210 * Recursive implementation of quantize() using the median cut algorithm.
2211 */
2212 void PNMImage::
2213r_quantize(pmap<xel, xel> &color_map, size_t max_colors,
2214 xel *colors, size_t num_colors) {
2215 if (num_colors <= max_colors) {
2216 // All points in this bucket can be preserved 1:1.
2217 for (size_t i = 0; i < num_colors; ++i) {
2218 const xel &col = colors[i];
2219 color_map[col] = col;
2220 }
2221 return;
2222 }
2223 else if (max_colors == 1) {
2224 // We've reached the target. Calculate the average, in linear space.
2225 LRGBColorf avg(0);
2226 for (size_t i = 0; i < num_colors; ++i) {
2227 avg += from_val(colors[i]);
2228 }
2229 avg *= 1.0f / num_colors;
2230 xel avg_val = to_val(avg);
2231
2232 // Map all colors in this bucket to the avg.
2233 for (size_t i = 0; i < num_colors; ++i) {
2234 color_map[colors[i]] = avg_val;
2235 }
2236 return;
2237 }
2238 else if (max_colors == 0) {
2239 // Not sure how this happens, but we can't preserve any color here.
2240 return;
2241 }
2242
2243 // Find the minimum/maximum RGB values. We should probably do this in
2244 // linear space, but eh.
2245 xelval min_r = _maxval;
2246 xelval min_g = _maxval;
2247 xelval min_b = _maxval;
2248 xelval max_r = 0, max_g = 0, max_b = 0;
2249 for (size_t i = 0; i < num_colors; ++i) {
2250 const xel &col = colors[i];
2251 min_r = std::min(min_r, col.r);
2252 max_r = std::max(max_r, col.r);
2253 min_g = std::min(min_g, col.g);
2254 max_g = std::max(max_g, col.g);
2255 min_b = std::min(min_b, col.b);
2256 max_b = std::max(max_b, col.b);
2257 }
2258
2259 int diff_r = max_r - min_r;
2260 int diff_g = max_g - min_g;
2261 int diff_b = max_b - min_b;
2262
2263 auto sort_by_red = [](const xel &c1, const xel &c2) {
2264 return c1.r < c2.r;
2265 };
2266 auto sort_by_green = [](const xel &c1, const xel &c2) {
2267 return c1.g < c2.g;
2268 };
2269 auto sort_by_blue = [](const xel &c1, const xel &c2) {
2270 return c1.b < c2.b;
2271 };
2272
2273 // Sort by the component with the most variation.
2274 if (diff_g >= diff_r) {
2275 if (diff_g >= diff_b) {
2276 std::sort(colors, colors + num_colors, sort_by_green);
2277 } else {
2278 std::sort(colors, colors + num_colors, sort_by_blue);
2279 }
2280 } else if (diff_r >= diff_b) {
2281 std::sort(colors, colors + num_colors, sort_by_red);
2282 } else {
2283 std::sort(colors, colors + num_colors, sort_by_blue);
2284 }
2285
2286 // Subdivide the sorted colors into two buckets, and recurse.
2287 size_t max_colors_1 = max_colors / 2;
2288 size_t max_colors_2 = max_colors - max_colors_1;
2289 size_t num_colors_1 = num_colors / 2;
2290 size_t num_colors_2 = num_colors - num_colors_1;
2291 r_quantize(color_map, max_colors_1, colors, num_colors_1);
2292 r_quantize(color_map, max_colors_2, colors + num_colors_1, num_colors_2);
2293}
2294
2295/**
2296 * Recursively fills in the minimum distance measured from a certain set of
2297 * points into the gray channel.
2298 */
2299 void PNMImage::
2300 do_fill_distance(int xi, int yi, int d) {
2301 if (xi < 0 || xi >= get_x_size() ||
2302 yi < 0 || yi >= get_y_size()) {
2303 return;
2304 }
2305 if (get_gray_val(xi, yi) <= d) {
2306 return;
2307 }
2308 set_gray_val(xi, yi, d);
2309
2310 do_fill_distance(xi + 1, yi, d + 1);
2311 do_fill_distance(xi - 1, yi, d + 1);
2312 do_fill_distance(xi, yi + 1, d + 1);
2313 do_fill_distance(xi, yi - 1, d + 1);
2314}
2315
2316/**
2317 * Returns the average color of all of the pixels in the image.
2318 */
2319LRGBColorf PNMImage::
2320 get_average_xel() const {
2321 LRGBColorf color (LRGBColorf::zero());
2322 if (_x_size == 0 || _y_size == 0) {
2323 return color;
2324 }
2325
2326 float factor = 1.0f / (float)(_x_size * _y_size);
2327
2328 int x, y;
2329 for (x = 0; x < _x_size; ++x) {
2330 for (y = 0; y < _y_size; ++y) {
2331 color += get_xel(x, y) * factor;
2332 }
2333 }
2334
2335 return color;
2336}
2337
2338/**
2339 * Returns the average color of all of the pixels in the image, including the
2340 * alpha channel.
2341 */
2342LColorf PNMImage::
2343 get_average_xel_a() const {
2344 LColorf color (LColorf::zero());
2345 if (_x_size == 0 || _y_size == 0) {
2346 return color;
2347 }
2348
2349 float factor = 1.0f / (float)(_x_size * _y_size);
2350
2351 int x, y;
2352 for (x = 0; x < _x_size; ++x) {
2353 for (y = 0; y < _y_size; ++y) {
2354 color += get_xel_a(x, y) * factor;
2355 }
2356 }
2357
2358 return color;
2359}
2360
2361/**
2362 * Returns the average grayscale component of all of the pixels in the image.
2363 */
2364 float PNMImage::
2365 get_average_gray() const {
2366 float gray = 0.0;
2367 if (_x_size == 0 || _y_size == 0) {
2368 return gray;
2369 }
2370
2371 int x, y;
2372 for (x = 0; x < _x_size; ++x) {
2373 for (y = 0; y < _y_size; ++y) {
2374 gray += get_gray(x, y);
2375 }
2376 }
2377
2378 gray /= (float)(_x_size * _y_size);
2379 return gray;
2380}
2381
2382/**
2383 * Returns a new PNMImage that is the complement of this PNMImage. This
2384 * operation is not color-space correct.
2385 */
2386PNMImage PNMImage::
2387 operator ~ () const {
2388 PNMImage target (*this);
2389 size_t array_size = _x_size * _y_size;
2390
2391 if (_array != nullptr && _alpha != nullptr) {
2392 for (size_t i = 0; i < array_size; ++i) {
2393 target._array[i].r = _maxval - _array[i].r;
2394 target._array[i].g = _maxval - _array[i].g;
2395 target._array[i].b = _maxval - _array[i].b;
2396 target._alpha[i] = _maxval - _alpha[i];
2397 }
2398 } else if (_array != nullptr) {
2399 for (size_t i = 0; i < array_size; ++i) {
2400 target._array[i].r = _maxval - _array[i].r;
2401 target._array[i].g = _maxval - _array[i].g;
2402 target._array[i].b = _maxval - _array[i].b;
2403 }
2404 } else if (_alpha != nullptr) {
2405 for (size_t i = 0; i < array_size; ++i) {
2406 target._alpha[i] = _maxval - _alpha[i];
2407 }
2408 }
2409 return target;
2410}
2411
2412/**
2413 * Sets each pixel value to the sum of the corresponding pixel values in the
2414 * two given images. Only valid when both images have the same size.
2415 */
2416 void PNMImage::
2417 operator += (const PNMImage &other) {
2418 nassertv(is_valid() && other.is_valid());
2419 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2420
2421 if (get_maxval() == other.get_maxval() &&
2422 get_color_space() == CS_linear &&
2423 other.get_color_space() == CS_linear) {
2424 size_t array_size = _x_size * _y_size;
2425
2426 // Simple case: add vals directly.
2427 if (_alpha != nullptr && other._alpha != nullptr) {
2428 for (size_t i = 0; i < array_size; ++i) {
2429 _array[i].r = clamp_val((int)_array[i].r + (int)other._array[i].r);
2430 _array[i].g = clamp_val((int)_array[i].g + (int)other._array[i].g);
2431 _array[i].b = clamp_val((int)_array[i].b + (int)other._array[i].b);
2432 _alpha[i] = clamp_val((int)_alpha[i] + (int)other._alpha[i]);
2433 }
2434 } else {
2435 for (size_t i = 0; i < array_size; ++i) {
2436 _array[i].r = clamp_val((int)_array[i].r + (int)other._array[i].r);
2437 _array[i].g = clamp_val((int)_array[i].g + (int)other._array[i].g);
2438 _array[i].b = clamp_val((int)_array[i].b + (int)other._array[i].b);
2439 }
2440 }
2441 } else {
2442 // Not a linear color space: convert back and forth.
2443 int x, y;
2444 for (x = 0; x < _x_size; ++x) {
2445 for (y = 0; y < _y_size; ++y) {
2446 set_xel_a(x, y, get_xel_a(x, y) + other.get_xel_a(x, y));
2447 }
2448 }
2449 }
2450}
2451
2452/**
2453 * Adds the provided color to each pixel in this image.
2454 */
2455 void PNMImage::
2456 operator += (const LColorf &other) {
2457 nassertv(is_valid());
2458
2459 if (get_color_space() == CS_linear) {
2460 size_t array_size = _x_size * _y_size;
2461
2462 // Note: don't use to_val here because it clamps values below 0
2463 int add_r = (int)(other.get_x() * get_maxval() + 0.5);
2464 int add_g = (int)(other.get_y() * get_maxval() + 0.5);
2465 int add_b = (int)(other.get_z() * get_maxval() + 0.5);
2466 int add_a = (int)(other.get_w() * get_maxval() + 0.5);
2467
2468 if (_alpha != nullptr) {
2469 for (size_t i = 0; i < array_size; ++i) {
2470 _array[i].r = clamp_val((int)_array[i].r + add_r);
2471 _array[i].g = clamp_val((int)_array[i].g + add_g);
2472 _array[i].b = clamp_val((int)_array[i].b + add_b);
2473 _alpha[i] = clamp_val((int)_alpha[i] + add_a);
2474 }
2475
2476 } else {
2477 for (size_t i = 0; i < array_size; ++i) {
2478 _array[i].r = clamp_val((int)_array[i].r + add_r);
2479 _array[i].g = clamp_val((int)_array[i].g + add_g);
2480 _array[i].b = clamp_val((int)_array[i].b + add_b);
2481 }
2482 }
2483 } else {
2484 // Not a linear color space: convert back and forth.
2485 int x, y;
2486 for (x = 0; x < _x_size; ++x) {
2487 for (y = 0; y < _y_size; ++y) {
2488 set_xel_a(x, y, get_xel_a(x, y) + other);
2489 }
2490 }
2491 }
2492}
2493
2494/**
2495 * Subtracts each pixel from the right image from each pixel value in this
2496 * image. Only valid when both images have the same size.
2497 */
2498 void PNMImage::
2499 operator -= (const PNMImage &other) {
2500 nassertv(is_valid() && other.is_valid());
2501 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2502
2503 if (get_maxval() == other.get_maxval() &&
2504 get_color_space() == CS_linear &&
2505 other.get_color_space() == CS_linear) {
2506 size_t array_size = _x_size * _y_size;
2507
2508 // Simple case: subtract vals directly.
2509 if (_alpha != nullptr && other._alpha != nullptr) {
2510 for (size_t i = 0; i < array_size; ++i) {
2511 _array[i].r = clamp_val((int)_array[i].r - (int)other._array[i].r);
2512 _array[i].g = clamp_val((int)_array[i].g - (int)other._array[i].g);
2513 _array[i].b = clamp_val((int)_array[i].b - (int)other._array[i].b);
2514 _alpha[i] = clamp_val((int)_alpha[i] - (int)other._alpha[i]);
2515 }
2516 } else {
2517 for (size_t i = 0; i < array_size; ++i) {
2518 _array[i].r = clamp_val((int)_array[i].r - (int)other._array[i].r);
2519 _array[i].g = clamp_val((int)_array[i].g - (int)other._array[i].g);
2520 _array[i].b = clamp_val((int)_array[i].b - (int)other._array[i].b);
2521 }
2522 }
2523 } else {
2524 // Not a linear color space: convert back and forth.
2525 int x, y;
2526 for (x = 0; x < _x_size; ++x) {
2527 for (y = 0; y < _y_size; ++y) {
2528 set_xel_a(x, y, get_xel_a(x, y) - other.get_xel_a(x, y));
2529 }
2530 }
2531 }
2532}
2533
2534/**
2535 * Subtracts the provided color from each pixel in this image.
2536 */
2537 void PNMImage::
2538 operator -= (const LColorf &other) {
2539 (*this) += -other;
2540}
2541
2542/**
2543 * Multiples each pixel in this image by each pixel value from the right
2544 * image. Note that the floating-point values in the 0..1 range are
2545 * multiplied, not in the 0..maxval range. Only valid when both images have
2546 * the same size.
2547 */
2548 void PNMImage::
2549 operator *= (const PNMImage &other) {
2550 nassertv(is_valid() && other.is_valid());
2551 nassertv(_x_size == other._x_size && _y_size == other._y_size);
2552
2553 int x, y;
2554 for (x = 0; x < _x_size; ++x) {
2555 for (y = 0; y < _y_size; ++y) {
2556 set_xel_a(x, y, get_xel_a(x, y) - other.get_xel_a(x, y));
2557 }
2558 }
2559}
2560
2561/**
2562 * Multiplies every pixel value in the image by a constant floating-point
2563 * multiplier value. This affects all channels.
2564 */
2565 void PNMImage::
2566 operator *= (float multiplier) {
2567 nassertv(is_valid());
2568
2569 if (get_color_space() == CS_linear) {
2570 size_t array_size = _x_size * _y_size;
2571
2572 if (_alpha != nullptr) {
2573 for (size_t i = 0; i < array_size; ++i) {
2574 _array[i].r = clamp_val((int)(_array[i].r * multiplier + 0.5f));
2575 _array[i].g = clamp_val((int)(_array[i].g * multiplier + 0.5f));
2576 _array[i].b = clamp_val((int)(_array[i].b * multiplier + 0.5f));
2577 _alpha[i] = clamp_val((int)(_alpha[i] * multiplier + 0.5f));
2578 }
2579
2580 } else {
2581 for (size_t i = 0; i < array_size; ++i) {
2582 _array[i].r = clamp_val((int)(_array[i].r * multiplier + 0.5f));
2583 _array[i].g = clamp_val((int)(_array[i].g * multiplier + 0.5f));
2584 _array[i].b = clamp_val((int)(_array[i].b * multiplier + 0.5f));
2585 }
2586 }
2587 } else {
2588 // Not a linear color space: convert back and forth.
2589 int x, y;
2590 for (x = 0; x < _x_size; ++x) {
2591 for (y = 0; y < _y_size; ++y) {
2592 set_xel_a(x, y, get_xel_a(x, y) * multiplier);
2593 }
2594 }
2595 }
2596}
2597
2598/**
2599 * Multiplies the provided color to each pixel in this image. This is a
2600 * component-wise multiplication.
2601 */
2602 void PNMImage::
2603 operator *= (const LColorf &other) {
2604 nassertv(is_valid());
2605
2606 if (get_color_space() == CS_linear) {
2607 size_t array_size = _x_size * _y_size;
2608
2609 if (_alpha != nullptr) {
2610 for (size_t i = 0; i < array_size; ++i) {
2611 _array[i].r = clamp_val((int)(_array[i].r * other[0] + 0.5f));
2612 _array[i].g = clamp_val((int)(_array[i].g * other[1] + 0.5f));
2613 _array[i].b = clamp_val((int)(_array[i].b * other[2] + 0.5f));
2614 _alpha[i] = clamp_val((int)(_alpha[i] * other[3] + 0.5f));
2615 }
2616
2617 } else {
2618 for (size_t i = 0; i < array_size; ++i) {
2619 _array[i].r = clamp_val((int)(_array[i].r * other[0] + 0.5f));
2620 _array[i].g = clamp_val((int)(_array[i].g * other[1] + 0.5f));
2621 _array[i].b = clamp_val((int)(_array[i].b * other[2] + 0.5f));
2622 }
2623 }
2624 } else {
2625 // Not a linear color space: convert back and forth.
2626 int x, y;
2627 for (x = 0; x < _x_size; ++x) {
2628 for (y = 0; y < _y_size; ++y) {
2629 LColorf color = get_xel_a(x, y);
2630 color.componentwise_mult(other);
2631 set_xel_a(x, y, color);
2632 }
2633 }
2634 }
2635}
Filename
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
PNMFileType
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition pnmFileType.h:32
PNMImageHeader::Histogram
Definition pnmImageHeader.h:167
PNMImageHeader::Histogram::swap
void swap(PixelCount &pixels, HistMap &hist_map)
Swaps the data in the Histogram with the indicated data.
Definition pnmImageHeader.I:501
PNMImageHeader::PixelSpecCount
Definition pnmImageHeader.h:153
PNMImageHeader::PixelSpec
Definition pnmImageHeader.h:115
PNMImageHeader::make_writer
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...
Definition pnmImageHeader.cxx:248
PNMImageHeader::get_maxval
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
Definition pnmImageHeader.h:70
PNMImageHeader::get_x_size
int get_x_size() const
Returns the number of pixels in the X direction.
Definition pnmImageHeader.I:144
PNMImageHeader::make_reader
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...
Definition pnmImageHeader.cxx:80
PNMImageHeader::get_num_channels
get_num_channels
Returns the number of channels in the image.
Definition pnmImageHeader.h:60
PNMImageHeader::has_alpha
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
Definition pnmImageHeader.I:106
PNMImageHeader::get_y_size
int get_y_size() const
Returns the number of pixels in the Y direction.
Definition pnmImageHeader.I:153
PNMImageHeader::has_alpha
bool has_alpha() const
Returns true if the image includes an alpha channel, false otherwise.
Definition pnmImageHeader.I:116
PNMImageHeader::get_type
get_type
If the file type is known (e.g.
Definition pnmImageHeader.h:85
PNMImageHeader::is_grayscale
bool is_grayscale() const
Returns false if the image is a full-color image, and has red, green, and blue components; true if it...
Definition pnmImageHeader.I:97
PNMImageHeader::get_color_type
ColorType get_color_type() const
Returns the image type of the image, as an enumerated value.
Definition pnmImageHeader.I:67
PNMImage::clear
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition pnmImage.cxx:48
PNMImage::get_xel_val
xel & get_xel_val(int x, int y)
Returns the RGB color at the indicated pixel.
Definition pnmImage.I:398
PNMImage::set_xel_val
void set_xel_val(int x, int y, const xel &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:419
PNMImage::set_maxval
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition pnmImage.cxx:794
PNMImage::get_channel_val
xelval get_channel_val(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
Definition pnmImage.cxx:837
PNMImage::indirect_1d_lookup
void indirect_1d_lookup(const PNMImage &index_image, int channel, const PNMImage &pixel_values)
index_image is a WxH grayscale image, while pixel_values is an Nx1 color (or grayscale) image.
Definition pnmImage.cxx:1679
PNMImage::copy_sub_image
void copy_sub_image(const PNMImage &copy, int xto, int yto, int xfrom=0, int yfrom=0, int x_size=-1, int y_size=-1)
Copies a rectangular area of another image into a rectangular area of this image.
Definition pnmImage.cxx:1077
PNMImage::render_spot
void render_spot(const LColorf &fg, const LColorf &bg, float min_radius, float max_radius)
Renders a solid-color circle, with a fuzzy edge, into the center of the PNMImage.
Definition pnmImage.cxx:1789
PNMImage::flip
void flip(bool flip_x, bool flip_y, bool transpose)
Reverses, transposes, and/or rotates the image in-place according to the specified parameters.
Definition pnmImage.cxx:715
PNMImage::set_blue
void set_blue(int x, int y, float b)
Sets the blue component color only at the indicated pixel.
Definition pnmImage.I:836
PNMImage::set_blue_val
void set_blue_val(int x, int y, xelval b)
Sets the blue component color only at the indicated pixel.
Definition pnmImage.I:530
PNMImage::premultiply_alpha
void premultiply_alpha()
Converts an image in-place to its "premultiplied" form, where, for every pixel in the image,...
Definition pnmImage.cxx:639
PNMImage::operator-=
void operator-=(const PNMImage &other)
Subtracts each pixel from the right image from each pixel value in this image.
Definition pnmImage.cxx:2499
PNMImage::fill_val
void fill_val(xelval red, xelval green, xelval blue)
Sets the entire image (except the alpha channel) to the given color.
Definition pnmImage.cxx:244
PNMImage::operator*=
void operator*=(const PNMImage &other)
Multiples each pixel in this image by each pixel value from the right image.
Definition pnmImage.cxx:2549
PNMImage::alpha_fill
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
Definition pnmImage.I:272
PNMImage::set_alpha_val
void set_alpha_val(int x, int y, xelval a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:559
PNMImage::get_green_val
xelval get_green_val(int x, int y) const
Returns the green component color at the indicated pixel.
Definition pnmImage.I:462
PNMImage::get_average_gray
float get_average_gray() const
Returns the average grayscale component of all of the pixels in the image.
Definition pnmImage.cxx:2365
PNMImage::make_grayscale
void make_grayscale()
Converts the image from RGB to grayscale.
Definition pnmImage.I:380
PNMImage::blend_sub_image
void blend_sub_image(const PNMImage &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 alpha channel of the copy is used to blend the copy into th...
Definition pnmImage.cxx:1132
PNMImage::set_green
void set_green(int x, int y, float g)
Sets the green component color only at the indicated pixel.
Definition pnmImage.I:827
PNMImage::get_blue
float get_blue(int x, int y) const
Returns the blue component color at the indicated pixel.
Definition pnmImage.I:788
PNMImage::get_alpha
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition pnmImage.I:809
PNMImage::get_xel
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition pnmImage.I:569
PNMImage::set_xel_a
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition pnmImage.I:675
PNMImage::expand_border
void expand_border(int left, int right, int bottom, int top, const LColorf &color)
Expands the image by the indicated number of pixels on each edge.
Definition pnmImage.cxx:1867
PNMImage::get_channel
float get_channel(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
Definition pnmImage.cxx:904
PNMImage::from_val
LRGBColorf from_val(const xel &input_value) const
A handy function to scale non-alpha values from [0..get_maxval()] to [0..1].
Definition pnmImage.I:171
PNMImage::set_color_space
void set_color_space(ColorSpace color_space)
Converts the colors in the image to the indicated color space.
Definition pnmImage.cxx:534
PNMImage::get_gray
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:799
PNMImage::threshold
void threshold(const PNMImage &select_image, int channel, float threshold, const PNMImage &lt, const PNMImage &ge)
Selectively copies each pixel from either one source or another source, depending on the pixel value ...
Definition pnmImage.cxx:1384
PNMImage::quick_filter_from
void quick_filter_from(const PNMImage &copy, int xborder=0, int yborder=0)
Resizes from the given image, with a fixed radius of 0.5.
Definition pnm-image-filter.cxx:754
PNMImage::lighten_sub_image
void lighten_sub_image(const PNMImage &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(), but the resulting color will be the lighter of the source and destinat...
Definition pnmImage.cxx:1308
PNMImage::clamp_val
xelval clamp_val(int input_value) const
A handy function to clamp values to [0..get_maxval()].
Definition pnmImage.I:70
PNMImage::fill
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
Definition pnmImage.I:246
PNMImage::blend
void blend(int x, int y, const LRGBColorf &val, float alpha)
Smoothly blends the indicated pixel value in with whatever was already in the image,...
Definition pnmImage.I:900
PNMImage::set_array
void set_array(xel *array)
Replaces the underlying PNMImage array with the indicated pointer.
Definition pnmImage.cxx:1046
PNMImage::get_xel_a
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition pnmImage.I:608
PNMImage::get_average_xel_a
LColorf get_average_xel_a() const
Returns the average color of all of the pixels in the image, including the alpha channel.
Definition pnmImage.cxx:2343
PNMImage::set_pixel
void set_pixel(int x, int y, const PixelSpec &pixel)
Sets the (r, g, b, a) pixel value at the indicated pixel, using a PixelSpec object.
Definition pnmImage.cxx:988
PNMImage::set_gray
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
Definition pnmImage.I:849
PNMImage::set_color_type
void set_color_type(ColorType color_type)
Translates the image to or from grayscale, color, or four-color mode.
Definition pnmImage.cxx:478
PNMImage::copy_channel_bits
void copy_channel_bits(const PNMImage &copy, int src_channel, int dest_channel, xelval src_mask, int right_shift)
Copies some subset of the bits of the specified channel from one image into some subset of the bits o...
Definition pnmImage.cxx:154
PNMImage::set_gray_val
void set_gray_val(int x, int y, xelval gray)
Sets the gray component color at the indicated pixel.
Definition pnmImage.I:545
PNMImage::get_alpha_val
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition pnmImage.I:494
PNMImage::set_red
void set_red(int x, int y, float r)
Sets the red component color only at the indicated pixel.
Definition pnmImage.I:818
PNMImage::copy_header_from
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
Definition pnmImage.cxx:200
PNMImage::operator~
PNMImage operator~() const
Returns a new PNMImage that is the complement of this PNMImage.
Definition pnmImage.cxx:2387
PNMImage::take_from
void take_from(PNMImage &orig)
Move the contents of the other image into this one, and empty the other image.
Definition pnmImage.cxx:224
PNMImage::reverse_rows
void reverse_rows()
Performs an in-place reversal of the row (y) data.
Definition pnmImage.cxx:685
PNMImage::get_bright
float get_bright(int x, int y) const
Returns the linear brightness of the given xel, as a linearized float in the range 0....
Definition pnmImage.I:869
PNMImage::has_read_size
bool has_read_size() const
Returns true if set_read_size() has been called.
Definition pnmImage.I:306
PNMImage::quantize
void quantize(size_t max_colors)
Reduces the number of unique colors in the image to (at most) the given count.
Definition pnmImage.cxx:1941
PNMImage::fill_distance_inside
void fill_distance_inside(const PNMImage &mask, float threshold, int radius, bool shrink_from_border)
Replaces this image with a grayscale image whose gray channel represents the linear Manhattan distanc...
Definition pnmImage.cxx:1602
PNMImage::add_sub_image
void add_sub_image(const PNMImage &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 pnmImage.cxx:1168
PNMImage::set_channel_val
void set_channel_val(int x, int y, int channel, xelval value)
Sets the nth component color at the indicated pixel.
Definition pnmImage.cxx:868
PNMImage::set_green_val
void set_green_val(int x, int y, xelval g)
Sets the green component color only at the indicated pixel.
Definition pnmImage.I:518
PNMImage::unfiltered_stretch_from
void unfiltered_stretch_from(const PNMImage &copy)
Resizes from the indicated image into this one by performing a nearest- point sample.
Definition pnmImage.cxx:1887
PNMImage::is_valid
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
PNMImage::remix_channels
void remix_channels(const LMatrix4 &conv)
Transforms every pixel using the operation (Ro,Go,Bo) = conv.xform_point(Ri,Gi,Bi); Input must be a c...
Definition pnmImage.cxx:2016
PNMImage::copy_from
void copy_from(const PNMImage &copy)
Makes this image become a copy of the other image.
Definition pnmImage.cxx:105
PNMImage::make_histogram
void make_histogram(Histogram &hist)
Computes a histogram of the colors used in the image.
Definition pnmImage.cxx:1911
PNMImage::set_alpha_array
void set_alpha_array(xelval *alpha)
Replaces the underlying PNMImage alpha array with the indicated pointer.
Definition pnmImage.cxx:1062
PNMImage::operator+=
void operator+=(const PNMImage &other)
Sets each pixel value to the sum of the corresponding pixel values in the two given images.
Definition pnmImage.cxx:2417
PNMImage::get_blue_val
xelval get_blue_val(int x, int y) const
Returns the blue component color at the indicated pixel.
Definition pnmImage.I:472
PNMImage::get_green
float get_green(int x, int y) const
Returns the green component color at the indicated pixel.
Definition pnmImage.I:779
PNMImage::unpremultiply_alpha
void unpremultiply_alpha()
Converts an image in-place to its "straight alpha" form (presumably from a "premultiplied" form),...
Definition pnmImage.cxx:663
PNMImage::read
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition pnmImage.cxx:278
PNMImage::get_average_xel
LRGBColorf get_average_xel() const
Returns the average color of all of the pixels in the image.
Definition pnmImage.cxx:2320
PNMImage::mult_sub_image
void mult_sub_image(const PNMImage &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 pnmImage.cxx:1202
PNMImage::fill_distance_outside
void fill_distance_outside(const PNMImage &mask, float threshold, int radius)
Replaces this image with a grayscale image whose gray channel represents the linear Manhattan distanc...
Definition pnmImage.cxx:1646
PNMImage::get_red_val
xelval get_red_val(int x, int y) const
Returns the red component color at the indicated pixel.
Definition pnmImage.I:452
PNMImage::rescale
void rescale(float min_val, float max_val)
Rescales the RGB channel values so that any values in the original image between min_val and max_val ...
Definition pnmImage.cxx:1703
PNMImage::set_xel
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:579
PNMImage::set_red_val
void set_red_val(int x, int y, xelval r)
Sets the red component color only at the indicated pixel.
Definition pnmImage.I:506
PNMImage::set_channel
void set_channel(int x, int y, int channel, float value)
Sets the nth component color at the indicated pixel.
Definition pnmImage.cxx:935
PNMImage::to_val
xel to_val(const LRGBColorf &input_value) const
A handy function to scale non-alpha values from [0..1] to [0..get_maxval()].
Definition pnmImage.I:79
PNMImage::do_fill_distance
void do_fill_distance(int xi, int yi, int d)
Recursively fills in the minimum distance measured from a certain set of points into the gray channel...
Definition pnmImage.cxx:2300
PNMImage::get_pixel
PixelSpec get_pixel(int x, int y) const
Returns the (r, g, b, a) pixel value at the indicated pixel, using a PixelSpec object.
Definition pnmImage.cxx:968
PNMImage::get_gray_val
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:484
PNMImage::set_alpha
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:859
PNMImage::get_color_space
ColorSpace get_color_space() const
Returns the color space in which the image is encoded.
Definition pnmImage.I:332
PNMImage::apply_exponent
void apply_exponent(float gray_exponent)
Adjusts each channel of the image by raising the corresponding component value to the indicated expon...
Definition pnmImage.I:947
PNMImage::add_alpha
void add_alpha()
Adds an alpha channel to the image, if it does not already have one.
Definition pnmImage.I:363
PNMImage::darken_sub_image
void darken_sub_image(const PNMImage &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(), but the resulting color will be the darker of the source and destinati...
Definition pnmImage.cxx:1241
PNMImage::perlin_noise_fill
void perlin_noise_fill(float sx, float sy, int table_size=256, unsigned long seed=0)
Fills the image with a grayscale perlin noise pattern based on the indicated parameters.
Definition pnmImage.cxx:1983
PNMImage::write
bool write(const Filename &filename, PNMFileType *type=nullptr) const
Writes the image to the indicated filename.
Definition pnmImage.cxx:385
PNMImage::alpha_fill_val
void alpha_fill_val(xelval alpha=0)
Sets the entire alpha channel to the given level.
Definition pnmImage.cxx:258
PNMImage::copy_channel
void copy_channel(const PNMImage &copy, int src_channel, int dest_channel)
Copies a channel from one image into another.
Definition pnmImage.cxx:121
PNMImage::get_red
float get_red(int x, int y) const
Returns the red component color at the indicated pixel.
Definition pnmImage.I:770
PNMReader
This is an abstract base class that defines the interface for reading image files of various types.
Definition pnmReader.h:27
PNMReader::read_data
virtual int read_data(xel *array, xelval *alpha)
Reads in an entire image all at once, storing it in the pre-allocated _x_size * _y_size array and alp...
Definition pnmReader.cxx:94
PNMReader::is_floating_point
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
PNMReader::prepare_read
virtual void prepare_read()
This method will be called before read_data() or read_row() is called.
Definition pnmReader.cxx:44
PNMReader::is_valid
bool is_valid() const
Returns true if the PNMReader can be used to read data, false if something is wrong.
Definition pnmReader.I:53
PNMReader::set_read_size
void set_read_size(int x_size, int y_size)
Instructs the reader to attempt to scale the image to the indicated size while reading it.
Definition pnmReader.I:34
PNMReader::read_pfm
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition pnmReader.cxx:80
PNMWriter
This is an abstract base class that defines the interface for writing image files of various types.
Definition pnmWriter.h:27
PNMWriter::copy_header_from
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
PNMWriter::write_data
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
PNMWriter::write_pfm
virtual bool write_pfm(const PfmFile &pfm)
Writes floating-point data from the indicated PfmFile.
Definition pnmWriter.cxx:53
PNMWriter::supports_grayscale
virtual bool supports_grayscale() const
Returns true if this particular PNMWriter understands grayscale images.
Definition pnmWriter.cxx:112
PNMWriter::supports_integer
virtual bool supports_integer()
Returns true if this PNMFileType can accept an integer image type, false if it can only accept a floa...
Definition pnmWriter.cxx:44
PerlinNoise2
This class provides an implementation of Perlin noise for 2 variables.
Definition perlinNoise2.h:25
PerlinNoise2::noise
double noise(double x, double y) const
Returns the noise function of the three inputs.
Definition perlinNoise2.I:100
PfmFile
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition pfmFile.h:31
PfmFile::store
bool store(PNMImage &pnmimage) const
Copies the data to the indicated PNMImage, converting to RGB values.
Definition pfmFile.cxx:360
PfmFile::load
bool load(const PNMImage &pnmimage)
Fills the PfmFile with the data from the indicated PNMImage, converted to floating-point values.
Definition pfmFile.cxx:287
StackedPerlinNoise2
Implements a multi-layer PerlinNoise, with one or more high-frequency noise functions added to a lowe...
Definition stackedPerlinNoise2.h:25
StackedPerlinNoise2::noise
double noise(double x, double y)
Returns the noise function of the three inputs.
Definition stackedPerlinNoise2.I:26
array
STL class.
config_pnmimage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
encode_sRGB_uchar
EXPCL_PANDA_PNMIMAGE unsigned char encode_sRGB_uchar(unsigned char val)
Encodes the linearized unsigned char value to an sRGB-encoded unsigned char value.
Definition convert_srgb.I:80
decode_sRGB_uchar
EXPCL_PANDA_PNMIMAGE unsigned char decode_sRGB_uchar(unsigned char val)
Decodes the sRGB-encoded unsigned char value to a linearized unsigned char value.
Definition convert_srgb.I:37
perlinNoise2.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pfmFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmBrush.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmImage.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmWriter.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
stackedPerlinNoise2.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pixel
Definition pnmimage_base.h:41