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