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
24using std::max;
25using 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 */
48clear() {
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 */
74clear(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 */
105copy_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 */
121copy_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 */
154copy_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 */
200copy_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 */
224take_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 */
244fill_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 */
258alpha_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 */
278read(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 */
301read(std::istream &data, const std::string &filename, PNMFileType *type,
302 bool report_unknown_type) {
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 */
321read(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(),
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 */
385write(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 */
408write(std::ostream &data, const std::string &filename, PNMFileType *type) const {
409 if (!is_valid()) {
410 return false;
411 }
412
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 */
430write(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 */
478set_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 */
534set_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 */
615make_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 */
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 */
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 */
685reverse_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 */
715flip(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 */
794set_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 */
837get_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 */
868set_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 */
904get_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 */
935set_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 */
968get_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 */
988set_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 */
1004blend(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 */
1046set_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 */
1062set_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 */
1077copy_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 */
1132blend_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 */
1168add_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 */
1202mult_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 */
1241darken_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 */
1308lighten_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 */
1384threshold(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 */
1602fill_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 */
1646fill_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 */
1679indirect_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 */
1703rescale(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 */
1733copy_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 */
1789render_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 */
1867expand_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,
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 */
1887unfiltered_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 */
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 */
1941quantize(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 */
1983perlin_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 */
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 */
2016remix_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 */
2035apply_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 */
2125void 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 */
2144void 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 */
2212void 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 */
2300do_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::
2320get_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 */
2343get_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 */
2365get_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 */
2387operator ~ () 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 */
2417operator += (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 */
2456operator += (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 */
2499operator -= (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 */
2538operator -= (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 */
2549operator *= (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 */
2566operator *= (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 */
2603operator *= (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}
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition pnmFileType.h:32
void swap(PixelCount &pixels, HistMap &hist_map)
Swaps the data in the Histogram with the indicated data.
This is the base class of PNMImage, PNMReader, and PNMWriter.
PNMWriter * make_writer(const Filename &filename, PNMFileType *type=nullptr) const
Returns a newly-allocated PNMWriter of the suitable type for writing an image to the indicated filena...
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
int get_x_size() const
Returns the number of pixels in the X direction.
PNMReader * make_reader(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true) const
Returns a newly-allocated PNMReader of the suitable type for reading from the indicated image filenam...
get_num_channels
Returns the number of channels in the image.
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
int get_y_size() const
Returns the number of pixels in the Y direction.
bool has_alpha() const
Returns true if the image includes an alpha channel, false otherwise.
get_type
If the file type is known (e.g.
bool is_grayscale() const
Returns false if the image is a full-color image, and has red, green, and blue components; true if it...
ColorType get_color_type() const
Returns the image type of the image, as an enumerated value.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition pnmImage.h:58
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
Definition pnmImage.cxx:48
xel & get_xel_val(int x, int y)
Returns the RGB color at the indicated pixel.
Definition pnmImage.I:398
void set_xel_val(int x, int y, const xel &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:419
void set_maxval(xelval maxval)
Rescales the image to the indicated maxval.
Definition pnmImage.cxx:794
xelval get_channel_val(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
Definition pnmImage.cxx:837
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.
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.
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.
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
void set_blue(int x, int y, float b)
Sets the blue component color only at the indicated pixel.
Definition pnmImage.I:836
void set_blue_val(int x, int y, xelval b)
Sets the blue component color only at the indicated pixel.
Definition pnmImage.I:530
void premultiply_alpha()
Converts an image in-place to its "premultiplied" form, where, for every pixel in the image,...
Definition pnmImage.cxx:639
void operator-=(const PNMImage &other)
Subtracts each pixel from the right image from each pixel value in this image.
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
void operator*=(const PNMImage &other)
Multiples each pixel in this image by each pixel value from the right image.
void alpha_fill(float alpha=0.0)
Sets the entire alpha channel to the given level.
Definition pnmImage.I:272
void set_alpha_val(int x, int y, xelval a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:559
xelval get_green_val(int x, int y) const
Returns the green component color at the indicated pixel.
Definition pnmImage.I:462
float get_average_gray() const
Returns the average grayscale component of all of the pixels in the image.
void make_grayscale()
Converts the image from RGB to grayscale.
Definition pnmImage.I:380
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...
void set_green(int x, int y, float g)
Sets the green component color only at the indicated pixel.
Definition pnmImage.I:827
float get_blue(int x, int y) const
Returns the blue component color at the indicated pixel.
Definition pnmImage.I:788
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition pnmImage.I:809
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition pnmImage.I:569
void set_xel_a(int x, int y, const LColorf &value)
Changes the RGBA color at the indicated pixel.
Definition pnmImage.I:675
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.
float get_channel(int x, int y, int channel) const
Returns the nth component color at the indicated pixel.
Definition pnmImage.cxx:904
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
void set_color_space(ColorSpace color_space)
Converts the colors in the image to the indicated color space.
Definition pnmImage.cxx:534
float get_gray(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:799
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 ...
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.
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...
xelval clamp_val(int input_value) const
A handy function to clamp values to [0..get_maxval()].
Definition pnmImage.I:70
void fill(float red, float green, float blue)
Sets the entire image (except the alpha channel) to the given color.
Definition pnmImage.I:246
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
void set_array(xel *array)
Replaces the underlying PNMImage array with the indicated pointer.
LColorf get_xel_a(int x, int y) const
Returns the RGBA color at the indicated pixel.
Definition pnmImage.I:608
LColorf get_average_xel_a() const
Returns the average color of all of the pixels in the image, including the alpha channel.
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
void set_gray(int x, int y, float gray)
Sets the gray component color at the indicated pixel.
Definition pnmImage.I:849
void set_color_type(ColorType color_type)
Translates the image to or from grayscale, color, or four-color mode.
Definition pnmImage.cxx:478
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
void set_gray_val(int x, int y, xelval gray)
Sets the gray component color at the indicated pixel.
Definition pnmImage.I:545
xelval get_alpha_val(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition pnmImage.I:494
void set_red(int x, int y, float r)
Sets the red component color only at the indicated pixel.
Definition pnmImage.I:818
void copy_header_from(const PNMImageHeader &header)
Copies just the header information into this image.
Definition pnmImage.cxx:200
PNMImage operator~() const
Returns a new PNMImage that is the complement of this PNMImage.
void take_from(PNMImage &orig)
Move the contents of the other image into this one, and empty the other image.
Definition pnmImage.cxx:224
void reverse_rows()
Performs an in-place reversal of the row (y) data.
Definition pnmImage.cxx:685
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
bool has_read_size() const
Returns true if set_read_size() has been called.
Definition pnmImage.I:306
void quantize(size_t max_colors)
Reduces the number of unique colors in the image to (at most) the given count.
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...
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,...
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
void set_green_val(int x, int y, xelval g)
Sets the green component color only at the indicated pixel.
Definition pnmImage.I:518
void unfiltered_stretch_from(const PNMImage &copy)
Resizes from the indicated image into this one by performing a nearest- point sample.
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
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...
void copy_from(const PNMImage &copy)
Makes this image become a copy of the other image.
Definition pnmImage.cxx:105
void make_histogram(Histogram &hist)
Computes a histogram of the colors used in the image.
void set_alpha_array(xelval *alpha)
Replaces the underlying PNMImage alpha array with the indicated pointer.
void operator+=(const PNMImage &other)
Sets each pixel value to the sum of the corresponding pixel values in the two given images.
xelval get_blue_val(int x, int y) const
Returns the blue component color at the indicated pixel.
Definition pnmImage.I:472
float get_green(int x, int y) const
Returns the green component color at the indicated pixel.
Definition pnmImage.I:779
void unpremultiply_alpha()
Converts an image in-place to its "straight alpha" form (presumably from a "premultiplied" form),...
Definition pnmImage.cxx:663
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition pnmImage.cxx:278
LRGBColorf get_average_xel() const
Returns the average color of all of the pixels in the 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...
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...
xelval get_red_val(int x, int y) const
Returns the red component color at the indicated pixel.
Definition pnmImage.I:452
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 ...
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:579
void set_red_val(int x, int y, xelval r)
Sets the red component color only at the indicated pixel.
Definition pnmImage.I:506
void set_channel(int x, int y, int channel, float value)
Sets the nth component color at the indicated pixel.
Definition pnmImage.cxx:935
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
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...
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
xelval get_gray_val(int x, int y) const
Returns the gray component color at the indicated pixel.
Definition pnmImage.I:484
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:859
ColorSpace get_color_space() const
Returns the color space in which the image is encoded.
Definition pnmImage.I:332
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
void add_alpha()
Adds an alpha channel to the image, if it does not already have one.
Definition pnmImage.I:363
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...
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.
bool write(const Filename &filename, PNMFileType *type=nullptr) const
Writes the image to the indicated filename.
Definition pnmImage.cxx:385
void alpha_fill_val(xelval alpha=0)
Sets the entire alpha channel to the given level.
Definition pnmImage.cxx:258
void copy_channel(const PNMImage &copy, int src_channel, int dest_channel)
Copies a channel from one image into another.
Definition pnmImage.cxx:121
float get_red(int x, int y) const
Returns the red component color at the indicated pixel.
Definition pnmImage.I:770
This is an abstract base class that defines the interface for reading image files of various types.
Definition pnmReader.h:27
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
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
virtual void prepare_read()
This method will be called before read_data() or read_row() is called.
Definition pnmReader.cxx:44
bool is_valid() const
Returns true if the PNMReader can be used to read data, false if something is wrong.
Definition pnmReader.I:53
void 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
virtual bool read_pfm(PfmFile &pfm)
Reads floating-point data directly into the indicated PfmFile.
Definition pnmReader.cxx:80
This is an abstract base class that defines the interface for writing image files of various types.
Definition pnmWriter.h:27
void copy_header_from(const PNMImageHeader &header)
Initializes all the data in the header (x_size, y_size, num_channels, etc.) to the same values indica...
Definition pnmWriter.I:83
virtual int write_data(xel *array, xelval *alpha)
Writes out an entire image all at once, including the header, based on the image data stored in the g...
Definition pnmWriter.cxx:74
virtual bool write_pfm(const PfmFile &pfm)
Writes floating-point data from the indicated PfmFile.
Definition pnmWriter.cxx:53
virtual bool supports_grayscale() const
Returns true if this particular PNMWriter understands grayscale images.
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
This class provides an implementation of Perlin noise for 2 variables.
double noise(double x, double y) const
Returns the noise function of the three inputs.
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition pfmFile.h:31
bool store(PNMImage &pnmimage) const
Copies the data to the indicated PNMImage, converting to RGB values.
Definition pfmFile.cxx:360
bool load(const PNMImage &pnmimage)
Fills the PfmFile with the data from the indicated PNMImage, converted to floating-point values.
Definition pfmFile.cxx:287
Implements a multi-layer PerlinNoise, with one or more high-frequency noise functions added to a lowe...
double noise(double x, double y)
Returns the noise function of the three inputs.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
EXPCL_PANDA_PNMIMAGE unsigned char encode_sRGB_uchar(unsigned char val)
Encodes the linearized unsigned char value to an sRGB-encoded unsigned char value.
EXPCL_PANDA_PNMIMAGE unsigned char decode_sRGB_uchar(unsigned char val)
Decodes the sRGB-encoded unsigned char value to a linearized unsigned char value.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.