Panda3D
pnmFileTypePNG.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 pnmFileTypePNG.cxx
10  * @author drose
11  * @date 2004-03-16
12  */
13 
14 #include "pnmFileTypePNG.h"
15 
16 #ifdef HAVE_PNG
17 
18 #include "config_pnmimagetypes.h"
19 
20 #include "pnmFileTypeRegistry.h"
21 #include "bamReader.h"
22 #include "thread.h"
23 
24 using std::istream;
25 using std::ostream;
26 using std::string;
27 
28 static const char * const extensions_png[] = {
29  "png"
30 };
31 static const int num_extensions_png = sizeof(extensions_png) / sizeof(const char *);
32 
33 TypeHandle PNMFileTypePNG::_type_handle;
34 
35 static const int png_max_palette = 256;
36 
37 // This STL comparison functor is used in write_data(), below. It sorts the
38 // non-maxval alpha pixels to the front of the list.
39 class LowAlphaCompare {
40 public:
41  bool operator() (const PNMImageHeader::PixelSpec &a,
42  const PNMImageHeader::PixelSpec &b) {
43  if (a._alpha != b._alpha) {
44  return a._alpha < b._alpha;
45  }
46  return a < b;
47  }
48 };
49 
50 /**
51  *
52  */
53 PNMFileTypePNG::
54 PNMFileTypePNG() {
55  // This constructor may run at static init time, so we use the ->
56  // dereferencing convention on the notify category.
57  if (pnmimage_png_cat->is_debug()) {
58  pnmimage_png_cat->debug()
59  << "PNG version " << PNG_LIBPNG_VER << "\n";
60  }
61 }
62 
63 /**
64  * Returns a few words describing the file type.
65  */
66 string PNMFileTypePNG::
67 get_name() const {
68  return "PNG";
69 }
70 
71 /**
72  * Returns the number of different possible filename extensions associated
73  * with this particular file type.
74  */
75 int PNMFileTypePNG::
76 get_num_extensions() const {
77  return num_extensions_png;
78 }
79 
80 /**
81  * Returns the nth possible filename extension associated with this particular
82  * file type, without a leading dot.
83  */
84 string PNMFileTypePNG::
85 get_extension(int n) const {
86  nassertr(n >= 0 && n < num_extensions_png, string());
87  return extensions_png[n];
88 }
89 
90 /**
91  * Returns a suitable filename extension (without a leading dot) to suggest
92  * for files of this type, or empty string if no suggestions are available.
93  */
94 string PNMFileTypePNG::
95 get_suggested_extension() const {
96  return "png";
97 }
98 
99 /**
100  * Returns true if this particular file type uses a magic number to identify
101  * it, false otherwise.
102  */
103 bool PNMFileTypePNG::
104 has_magic_number() const {
105  return true;
106 }
107 
108 /**
109  * Returns true if the indicated "magic number" byte stream (the initial few
110  * bytes read from the file) matches this particular file type, false
111  * otherwise.
112  */
113 bool PNMFileTypePNG::
114 matches_magic_number(const string &magic_number) const {
115  return png_sig_cmp((png_bytep)magic_number.data(), 0, magic_number.length()) == 0;
116 }
117 
118 /**
119  * Allocates and returns a new PNMReader suitable for reading from this file
120  * type, if possible. If reading from this file type is not supported,
121  * returns NULL.
122  */
123 PNMReader *PNMFileTypePNG::
124 make_reader(istream *file, bool owns_file, const string &magic_number) {
125  init_pnm();
126  return new Reader(this, file, owns_file, magic_number);
127 }
128 
129 /**
130  * Allocates and returns a new PNMWriter suitable for reading from this file
131  * type, if possible. If writing files of this type is not supported, returns
132  * NULL.
133  */
134 PNMWriter *PNMFileTypePNG::
135 make_writer(ostream *file, bool owns_file) {
136  init_pnm();
137  return new Writer(this, file, owns_file);
138 }
139 
140 /**
141  * Registers the current object as something that can be read from a Bam file.
142  */
143 void PNMFileTypePNG::
144 register_with_read_factory() {
146  register_factory(get_class_type(), make_PNMFileTypePNG);
147 }
148 
149 /**
150  * This method is called by the BamReader when an object of this type is
151  * encountered in a Bam file; it should allocate and return a new object with
152  * all the data read.
153  *
154  * In the case of the PNMFileType objects, since these objects are all shared,
155  * we just pull the object from the registry.
156  */
157 TypedWritable *PNMFileTypePNG::
158 make_PNMFileTypePNG(const FactoryParams &params) {
159  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
160 }
161 
162 /**
163  *
164  */
165 PNMFileTypePNG::Reader::
166 Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
167  PNMReader(type, file, owns_file)
168 {
169  _png = nullptr;
170  _info = nullptr;
171  _is_valid = false;
172 
173  _png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
174  png_error, png_warning);
175  if (_png == nullptr) {
176  return;
177  }
178 
179  _info = png_create_info_struct(_png);
180  if (_info == nullptr) {
181  png_destroy_read_struct(&_png, nullptr, nullptr);
182  return;
183  }
184 
185  _is_valid = true;
186 
187  if (setjmp(_jmpbuf)) {
188  // This is the ANSI C way to handle exceptions. If setjmp(), above,
189  // returns true, it means that libpng detected an exception while
190  // executing the code that reads the header info, below.
191  free_png();
192  return;
193  }
194 
195  png_set_read_fn(_png, (void *)this, png_read_data);
196  png_set_sig_bytes(_png, magic_number.length());
197 
198  png_read_info(_png, _info);
199 
200  png_uint_32 width;
201  png_uint_32 height;
202  int bit_depth;
203  int color_type;
204  int srgb_intent;
205  double gamma;
206 
207  png_get_IHDR(_png, _info, &width, &height,
208  &bit_depth, &color_type, nullptr, nullptr, nullptr);
209 
210  // Look for an sRGB chunk.
211  if (png_get_sRGB(_png, _info, &srgb_intent) == PNG_INFO_sRGB) {
212  _color_space = CS_sRGB;
213 
214  } else if (png_get_gAMA(_png, _info, &gamma) == PNG_INFO_gAMA) {
215  // File specifies a gamma.
216  if (gamma >= 0.99 && gamma <= 1.01) {
217  _color_space = CS_linear;
218 
219  } else if (gamma >= 0.44999 && gamma <= 0.455001) {
220  // It's probably close enough to sRGB.
221  _color_space = CS_sRGB;
222 
223  } else {
224  pnmimage_png_cat.warning()
225  << "Unsupported image gamma " << gamma << ", "
226  << "please re-export image as sRGB or linear.\n";
227  }
228  }
229 
230  pnmimage_png_cat.debug()
231  << "width = " << width << " height = " << height << " bit_depth = "
232  << bit_depth << " color_type = " << color_type
233  << " color_space = " << _color_space << "\n";
234 
235  _x_size = width;
236  _y_size = height;
237  _maxval = (1 << bit_depth) - 1;
238 
239  if (bit_depth < 8) {
240  png_set_packing(_png);
241  }
242 
243  switch (color_type) {
244  case PNG_COLOR_TYPE_GRAY:
245  pnmimage_png_cat.debug()
246  << "PNG_COLOR_TYPE_GRAY\n";
247  _num_channels = 1;
248  break;
249 
250  case PNG_COLOR_TYPE_GRAY_ALPHA:
251  pnmimage_png_cat.debug()
252  << "PNG_COLOR_TYPE_GRAY_ALPHA\n";
253  _num_channels = 2;
254  break;
255 
256  case PNG_COLOR_TYPE_RGB:
257  pnmimage_png_cat.debug()
258  << "PNG_COLOR_TYPE_RGB\n";
259  _num_channels = 3;
260  break;
261 
262  case PNG_COLOR_TYPE_RGB_ALPHA:
263  pnmimage_png_cat.debug()
264  << "PNG_COLOR_TYPE_RGB_ALPHA\n";
265  _num_channels = 4;
266  break;
267 
268  case PNG_COLOR_TYPE_PALETTE:
269  pnmimage_png_cat.debug()
270  << "PNG_COLOR_TYPE_PALETTE\n";
271  png_set_palette_to_rgb(_png);
272  _maxval = 255;
273  _num_channels = 3;
274  break;
275 
276  default:
277  pnmimage_png_cat.error()
278  << "Unsupported color type: " << color_type << "\n";
279  free_png();
280  return;
281  }
282 
283  if (png_get_valid(_png, _info, PNG_INFO_tRNS)) {
284  png_set_tRNS_to_alpha(_png);
285  if (!has_alpha()) {
286  _num_channels++;
287  }
288  }
289 
290  png_read_update_info(_png, _info);
291 }
292 
293 /**
294  *
295  */
296 PNMFileTypePNG::Reader::
297 ~Reader() {
298  free_png();
299 }
300 
301 /**
302  * Reads in an entire image all at once, storing it in the pre-allocated
303  * _x_size * _y_size array and alpha pointers. (If the image type has no
304  * alpha channel, alpha is ignored.) Returns the number of rows correctly
305  * read.
306  *
307  * Derived classes need not override this if they instead provide
308  * supports_read_row() and read_row(), below.
309  */
310 int PNMFileTypePNG::Reader::
311 read_data(xel *array, xelval *alpha_data) {
312  if (!is_valid()) {
313  return 0;
314  }
315 
316  if (setjmp(_jmpbuf)) {
317  // This is the ANSI C way to handle exceptions. If setjmp(), above,
318  // returns true, it means that libpng detected an exception while
319  // executing the code that reads the image, below.
320  free_png();
321  return 0;
322  }
323 
324  int row_byte_length = _x_size * _num_channels;
325  if (_maxval > 255) {
326  row_byte_length *= 2;
327  }
328 
329  int num_rows = _y_size;
330 
331  if (pnmimage_png_cat.is_debug()) {
332  pnmimage_png_cat.debug()
333  << "Allocating " << num_rows << " rows of " << row_byte_length
334  << " bytes each.\n";
335  }
336 
337  // We need to read a full copy of the image in first, in libpng's 2-d array
338  // format, mainly because we keep array and alpha data separately, and there
339  // doesn't appear to be good support to get this stuff out row-at-a-time for
340  // interlaced files.
341  png_bytep *rows = (png_bytep *)alloca(num_rows * sizeof(png_bytep));
342  int yi;
343 
344  png_byte *alloc = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte) * num_rows);
345  for (yi = 0; yi < num_rows; yi++) {
346  rows[yi] = alloc + (row_byte_length * sizeof(png_byte)) * yi;
347  }
348 
349  png_read_image(_png, rows);
350 
351  bool get_color = !is_grayscale();
352  bool get_alpha = has_alpha();
353 
354  int pi = 0;
355  for (yi = 0; yi < num_rows; yi++) {
356  png_bytep source = rows[yi];
357  for (int xi = 0; xi < _x_size; xi++) {
358  int red = 0;
359  int green = 0;
360  int blue = 0;
361  int alpha = 0;
362 
363  if (_maxval > 255) {
364  if (get_color) {
365  red = (source[0] << 8) | source[1];
366  source += 2;
367 
368  green = (source[0] << 8) | source[1];
369  source += 2;
370  }
371 
372  blue = (source[0] << 8) | source[1];
373  source += 2;
374 
375  if (get_alpha) {
376  alpha = (source[0] << 8) | source[1];
377  source += 2;
378  }
379 
380  } else {
381  if (get_color) {
382  red = *source;
383  source++;
384 
385  green = *source;
386  source++;
387  }
388 
389  blue = *source;
390  source++;
391 
392  if (get_alpha) {
393  alpha = *source;
394  source++;
395  }
396  }
397 
398  PPM_ASSIGN(array[pi], red, green, blue);
399  if (get_alpha) {
400  alpha_data[pi] = alpha;
401  }
402  pi++;
403  }
404 
405  nassertr(source <= rows[yi] + row_byte_length, yi);
406  }
407 
408  png_read_end(_png, nullptr);
409  PANDA_FREE_ARRAY(alloc);
410 
411  return _y_size;
412 }
413 
414 /**
415  * Releases the internal PNG structures and marks the reader invalid.
416  */
417 void PNMFileTypePNG::Reader::
418 free_png() {
419  if (_is_valid) {
420  png_destroy_read_struct(&_png, &_info, nullptr);
421  _is_valid = false;
422  }
423 }
424 
425 /**
426  * A callback handler that PNG uses to read data from the iostream.
427  */
428 void PNMFileTypePNG::Reader::
429 png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
430  Reader *self = (Reader *)png_get_io_ptr(png_ptr);
431  self->_file->read((char *)data, length);
432  if (length != (png_size_t)self->_file->gcount()) {
433  pnmimage_png_cat.error()
434  << "Didn't read enough bytes.\n";
435  // Is there no way to indicate a read failure to libpng?
436  }
438 }
439 
440 /**
441  * This is our own warning handler. It is called by the png library to issue
442  * a warning message.
443  */
444 void PNMFileTypePNG::Reader::
445 png_warning(png_structp, png_const_charp warning_msg) {
446  pnmimage_png_cat.warning()
447  << warning_msg << "\n";
448 }
449 
450 /**
451  * This is our own error handler. It is called by the png library to issue a
452  * fatal error message.
453  */
454 void PNMFileTypePNG::Reader::
455 png_error(png_structp png_ptr, png_const_charp error_msg) {
456  pnmimage_png_cat.error()
457  << error_msg << "\n";
458 
459  // The PNG library insists we should not return, so instead of returning, we
460  // will do a longjmp out of the png code.
461  Reader *self = (Reader *)png_get_io_ptr(png_ptr);
462  if (self == nullptr) {
463  // Oops, we haven't got a self pointer yet. Return anyway and hope we'll
464  // be ok.
465  pnmimage_png_cat.error()
466  << "Returning before opening file.\n";
467  return;
468  }
469 
470  longjmp(self->_jmpbuf, true);
471 }
472 
473 /**
474  *
475  */
476 PNMFileTypePNG::Writer::
477 Writer(PNMFileType *type, ostream *file, bool owns_file) :
478  PNMWriter(type, file, owns_file)
479 {
480  _png = nullptr;
481  _info = nullptr;
482  _is_valid = false;
483 
484  _png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr,
485  png_error, png_warning);
486  if (_png == nullptr) {
487  return;
488  }
489 
490  _info = png_create_info_struct(_png);
491  if (_info == nullptr) {
492  png_destroy_write_struct(&_png, nullptr);
493  return;
494  }
495 
496  _is_valid = true;
497 }
498 
499 /**
500  *
501  */
502 PNMFileTypePNG::Writer::
503 ~Writer() {
504  free_png();
505 }
506 
507 /**
508  * Writes in an entire image all at once, storing it in the pre-allocated
509  * _x_size * _y_size array and alpha pointers. (If the image type has no
510  * alpha channel, alpha is ignored.) Returns the number of rows correctly
511  * write.
512  *
513  * Derived classes need not override this if they instead provide
514  * supports_write_row() and write_row(), below.
515  */
516 int PNMFileTypePNG::Writer::
517 write_data(xel *array, xelval *alpha_data) {
518  if (!is_valid()) {
519  return 0;
520  }
521 
522  if (setjmp(_jmpbuf)) {
523  // This is the ANSI C way to handle exceptions. If setjmp(), above,
524  // returns true, it means that libpng detected an exception while
525  // executing the code that writes the image, below.
526  free_png();
527  return 0;
528  }
529 
530  png_set_write_fn(_png, (void *)this, png_write_data, png_flush_data);
531 
532  // The compression level corresponds directly to the compression levels for
533  // zlib.
534  png_set_compression_level(_png, png_compression_level);
535 
536  // First, write the header.
537 
538  int true_bit_depth = pm_maxvaltobits(_maxval);
539  int png_bit_depth = make_png_bit_depth(true_bit_depth);
540 
541  png_color_8 sig_bit;
542  sig_bit.red = true_bit_depth;
543  sig_bit.green = true_bit_depth;
544  sig_bit.blue = true_bit_depth;
545  sig_bit.gray = true_bit_depth;
546  sig_bit.alpha = true_bit_depth;
547 
548  int color_type = 0;
549 
550  if (!is_grayscale()) {
551  color_type |= PNG_COLOR_MASK_COLOR;
552  }
553  if (has_alpha()) {
554  color_type |= PNG_COLOR_MASK_ALPHA;
555  }
556 
557  // Determine if we should make a palettized image out of this. In order for
558  // this to be possible and effective, we must have no more than 256 unique
559  // coloralpha combinations for a color image, and the resulting bitdepth
560  // should be smaller than what we would have otherwise.
561  Palette palette;
562  HistMap palette_lookup;
563  png_color png_palette_table[png_max_palette];
564  png_byte png_trans[png_max_palette];
565 
566  if (png_palette) {
567  if (png_bit_depth <= 8) {
568  if (compute_palette(palette, array, alpha_data, png_max_palette)) {
569  pnmimage_png_cat.debug()
570  << palette.size() << " colors found.\n";
571 
572  int palette_bit_depth = make_png_bit_depth(pm_maxvaltobits(palette.size() - 1));
573 
574  int total_bits = png_bit_depth;
575  if (!is_grayscale()) {
576  total_bits *= 3;
577  }
578  if (has_alpha()) {
579  total_bits += png_bit_depth;
580  }
581 
582  if (palette_bit_depth < total_bits ||
583  _maxval != (1 << true_bit_depth) - 1) {
584  pnmimage_png_cat.debug()
585  << "palette bit depth of " << palette_bit_depth
586  << " improves on bit depth of " << total_bits
587  << "; making a palette image.\n";
588 
589  color_type = PNG_COLOR_TYPE_PALETTE;
590 
591  // Re-sort the palette to put the semitransparent pixels at the
592  // beginning.
593  sort(palette.begin(), palette.end(), LowAlphaCompare());
594 
595  double palette_scale = 255.0 / _maxval;
596 
597  int num_alpha = 0;
598  for (int i = 0; i < (int)palette.size(); i++) {
599  png_palette_table[i].red = (int)(palette[i]._red * palette_scale + 0.5);
600  png_palette_table[i].green = (int)(palette[i]._green * palette_scale + 0.5);
601  png_palette_table[i].blue = (int)(palette[i]._blue * palette_scale + 0.5);
602  png_trans[i] = (int)(palette[i]._alpha * palette_scale + 0.5);
603  if (palette[i]._alpha != _maxval) {
604  num_alpha = i + 1;
605  }
606 
607  // Also build a reverse-lookup from color to palette index in the
608  // "histogram" structure.
609  palette_lookup[palette[i]] = i;
610  }
611 
612  png_set_PLTE(_png, _info, png_palette_table, palette.size());
613  if (has_alpha()) {
614  pnmimage_png_cat.debug()
615  << "palette contains " << num_alpha << " transparent entries.\n";
616  png_set_tRNS(_png, _info, png_trans, num_alpha, nullptr);
617  }
618  } else {
619  pnmimage_png_cat.debug()
620  << "palette bit depth of " << palette_bit_depth
621  << " does not improve on bit depth of " << total_bits
622  << "; not making a palette image.\n";
623  }
624 
625  } else {
626  pnmimage_png_cat.debug()
627  << "more than " << png_max_palette
628  << " colors found; not making a palette image.\n";
629  }
630  } else {
631  pnmimage_png_cat.debug()
632  << "maxval exceeds 255; not making a palette image.\n";
633  }
634  } else {
635  pnmimage_png_cat.debug()
636  << "palette images are not enabled.\n";
637  }
638 
639  pnmimage_png_cat.debug()
640  << "width = " << _x_size << " height = " << _y_size
641  << " maxval = " << _maxval << " bit_depth = "
642  << png_bit_depth << " color_type = " << color_type
643  << " color_space = " << _color_space << "\n";
644 
645  png_set_IHDR(_png, _info, _x_size, _y_size, png_bit_depth,
646  color_type, PNG_INTERLACE_NONE,
647  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
648 
649  // Set the true bit depth of the image data.
650  if (png_bit_depth != true_bit_depth || color_type == PNG_COLOR_TYPE_PALETTE) {
651  png_set_sBIT(_png, _info, &sig_bit);
652  }
653 
654  // Set the color space, if we know it.
655  switch (_color_space) {
656  case CS_linear:
657  png_set_gAMA(_png, _info, 1.0);
658  // Not sure if we should set cHRM to anything.
659  break;
660 
661  case CS_sRGB:
662  png_set_sRGB_gAMA_and_cHRM(_png, _info, PNG_sRGB_INTENT_RELATIVE);
663  break;
664 
665  default:
666  break;
667  }
668 
669  png_write_info(_png, _info);
670 
671 
672  // Now set up the transformations to write the image data.
673  if (png_bit_depth < 8) {
674  png_set_packing(_png);
675  }
676 
677  double val_scale = 1.0;
678 
679  if (color_type != PNG_COLOR_TYPE_PALETTE) {
680  if (png_bit_depth != true_bit_depth) {
681  png_set_shift(_png, &sig_bit);
682  }
683  // Since this assumes that _maxval is one less than a power of 2, we set
684  // val_scale to the appropriate factor in case it is not.
685  int png_maxval = (1 << png_bit_depth) - 1;
686  val_scale = (double)png_maxval / (double)_maxval;
687  }
688 
689  int row_byte_length = _x_size * _num_channels;
690  if (png_bit_depth > 8) {
691  row_byte_length *= 2;
692  }
693 
694  int num_rows = _y_size;
695 
696  if (pnmimage_png_cat.is_debug()) {
697  pnmimage_png_cat.debug()
698  << "Allocating one row of " << row_byte_length
699  << " bytes.\n";
700  }
701 
702  // When writing, we only need to copy the image out one row at a time,
703  // because we don't mess around with writing interlaced files. If we were
704  // writing an interlaced file, we'd have to copy the whole image first.
705 
706  png_bytep row = (png_byte *)PANDA_MALLOC_ARRAY(row_byte_length * sizeof(png_byte));
707 
708  bool save_color = !is_grayscale();
709  bool save_alpha = has_alpha();
710 
711  if (val_scale == 1.0) {
712  // No scale needed; we're already a power of 2.
713  int pi = 0;
714  for (int yi = 0; yi < num_rows; yi++) {
715  png_bytep dest = row;
716 
717  if (color_type == PNG_COLOR_TYPE_PALETTE) {
718  for (int xi = 0; xi < _x_size; xi++) {
719  int index;
720 
721  if (save_color) {
722  if (save_alpha) {
723  index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]), alpha_data[pi])];
724  } else {
725  index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]))];
726  }
727  } else {
728  if (save_alpha) {
729  index = palette_lookup[PixelSpec(PPM_GETB(array[pi]), alpha_data[pi])];
730  } else {
731  index = palette_lookup[PixelSpec(PPM_GETB(array[pi]))];
732  }
733  }
734 
735  *dest++ = index;
736  pi++;
737  }
738 
739  } else if (png_bit_depth > 8) {
740  for (int xi = 0; xi < _x_size; xi++) {
741  if (save_color) {
742  xelval red = PPM_GETR(array[pi]);
743  *dest++ = (red >> 8) & 0xff;
744  *dest++ = red & 0xff;
745  xelval green = PPM_GETG(array[pi]);
746  *dest++ = (green >> 8) & 0xff;
747  *dest++ = green & 0xff;
748  }
749  xelval blue = PPM_GETB(array[pi]);
750  *dest++ = (blue >> 8) & 0xff;
751  *dest++ = blue & 0xff;
752 
753  if (save_alpha) {
754  xelval alpha = alpha_data[pi];
755  *dest++ = (alpha >> 8) & 0xff;
756  *dest++ = alpha & 0xff;
757  }
758  pi++;
759  }
760 
761  } else {
762  for (int xi = 0; xi < _x_size; xi++) {
763  if (save_color) {
764  *dest++ = PPM_GETR(array[pi]);
765  *dest++ = PPM_GETG(array[pi]);
766  }
767 
768  *dest++ = PPM_GETB(array[pi]);
769 
770  if (save_alpha) {
771  *dest++ = alpha_data[pi];
772  }
773  pi++;
774  }
775  }
776 
777  nassertr(dest <= row + row_byte_length, yi);
778  png_write_row(_png, row);
780  }
781  } else {
782  // Here we might need to scale each component to match the png
783  // requirement.
784  nassertr(color_type != PNG_COLOR_TYPE_PALETTE, 0);
785  int pi = 0;
786  for (int yi = 0; yi < num_rows; yi++) {
787  png_bytep dest = row;
788 
789  if (png_bit_depth > 8) {
790  for (int xi = 0; xi < _x_size; xi++) {
791  if (save_color) {
792  xelval red = (xelval)(PPM_GETR(array[pi]) * val_scale + 0.5);
793  *dest++ = (red >> 8) & 0xff;
794  *dest++ = red & 0xff;
795  xelval green = (xelval)(PPM_GETG(array[pi]) * val_scale + 0.5);
796  *dest++ = (green >> 8) & 0xff;
797  *dest++ = green & 0xff;
798  }
799  xelval blue = (xelval)(PPM_GETB(array[pi]) * val_scale + 0.5);
800  *dest++ = (blue >> 8) & 0xff;
801  *dest++ = blue & 0xff;
802 
803  if (save_alpha) {
804  xelval alpha = (xelval)(alpha_data[pi] * val_scale + 0.5);
805  *dest++ = (alpha >> 8) & 0xff;
806  *dest++ = alpha & 0xff;
807  }
808  pi++;
809  }
810 
811  } else {
812  for (int xi = 0; xi < _x_size; xi++) {
813  if (save_color) {
814  *dest++ = (xelval)(PPM_GETR(array[pi]) * val_scale + 0.5);
815  *dest++ = (xelval)(PPM_GETG(array[pi]) * val_scale + 0.5);
816  }
817 
818  *dest++ = (xelval)(PPM_GETB(array[pi]) * val_scale + 0.5);
819 
820  if (save_alpha) {
821  *dest++ = (xelval)(alpha_data[pi] * val_scale + 0.5);
822  }
823  pi++;
824  }
825  }
826 
827  nassertr(dest <= row + row_byte_length, yi);
828  png_write_row(_png, row);
830  }
831  }
832  PANDA_FREE_ARRAY(row);
833 
834  png_write_end(_png, nullptr);
835 
836  return _y_size;
837 }
838 
839 /**
840  * Releases the internal PNG structures and marks the writer invalid.
841  */
842 void PNMFileTypePNG::Writer::
843 free_png() {
844  if (_is_valid) {
845  png_destroy_write_struct(&_png, &_info);
846  _is_valid = false;
847  }
848 }
849 
850 /**
851  * Elevates the indicated bit depth to one of the legal PNG bit depths: 1, 2,
852  * 4, 8, or 16.
853  */
854 int PNMFileTypePNG::Writer::
855 make_png_bit_depth(int bit_depth) {
856  switch (bit_depth) {
857  case 0:
858  case 1:
859  return 1;
860 
861  case 2:
862  return 2;
863 
864  case 3:
865  case 4:
866  return 4;
867 
868  case 5:
869  case 6:
870  case 7:
871  case 8:
872  return 8;
873 
874  default:
875  return 16;
876  }
877 }
878 
879 /**
880  * A callback handler that PNG uses to write data to the iostream.
881  */
882 void PNMFileTypePNG::Writer::
883 png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
884  Writer *self = (Writer *)png_get_io_ptr(png_ptr);
885  self->_file->write((char *)data, length);
886  if (self->_file->fail()) {
887  pnmimage_png_cat.error()
888  << "Unable to write to the iostream.\n";
889  // Is there no way to indicate a write failure to libpng?
890  }
891 }
892 
893 /**
894  * A callback handler that PNG uses to write data to the iostream.
895  */
896 void PNMFileTypePNG::Writer::
897 png_flush_data(png_structp png_ptr) {
898  Writer *self = (Writer *)png_get_io_ptr(png_ptr);
899  self->_file->flush();
900 }
901 
902 /**
903  * This is our own warning handler. It is called by the png library to issue
904  * a warning message.
905  */
906 void PNMFileTypePNG::Writer::
907 png_warning(png_structp, png_const_charp warning_msg) {
908  pnmimage_png_cat.warning()
909  << warning_msg << "\n";
910 }
911 
912 /**
913  * This is our own error handler. It is called by the png library to issue a
914  * fatal error message.
915  */
916 void PNMFileTypePNG::Writer::
917 png_error(png_structp png_ptr, png_const_charp error_msg) {
918  pnmimage_png_cat.error()
919  << error_msg << "\n";
920 
921  // The PNG library insists we should not return, so instead of returning, we
922  // will do a longjmp out of the png code.
923  Writer *self = (Writer *)png_get_io_ptr(png_ptr);
924  if (self == nullptr) {
925  // Oops, we haven't got a self pointer yet. Return anyway and hope we'll
926  // be ok.
927  pnmimage_png_cat.error()
928  << "Returning before opening file.\n";
929  return;
930  }
931 
932  longjmp(self->_jmpbuf, true);
933 }
934 
935 
936 #endif // HAVE_PNG
int pm_maxvaltobits(int maxval)
Returns the number of bits sufficient to hold the indicated maxval value.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition: thread.I:212
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PNMFileType * get_type_by_handle(TypeHandle handle) const
Returns the PNMFileType instance stored in the registry for the given TypeHandle, e....
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
This is an abstract base class that defines the interface for writing image files of various types.
Definition: pnmWriter.h:27
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
bool has_alpha() const
Returns true if the image includes an alpha channel, false otherwise.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81