diff --git a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx b/panda/src/pnmimagetypes/pnmFileTypePNG.cxx index 33c9e5f540..f561f68026 100755 --- a/panda/src/pnmimagetypes/pnmFileTypePNG.cxx +++ b/panda/src/pnmimagetypes/pnmFileTypePNG.cxx @@ -24,7 +24,6 @@ #include "pnmFileTypeRegistry.h" #include "bamReader.h" -#include "ppmcmap.h" static const char * const extensions_png[] = { "png" @@ -33,6 +32,21 @@ static const int num_extensions_png = sizeof(extensions_png) / sizeof(const char TypeHandle PNMFileTypePNG::_type_handle; +static const int png_max_palette = 256; + +// This STL comparison functor is used in write_data(), below. It +// sorts the non-maxval alpha pixels to the front of the list. +class LowAlphaCompare { +public: + bool operator() (const PNMImageHeader::PixelSpec &a, + const PNMImageHeader::PixelSpec &b) { + if (a._alpha != b._alpha) { + return a._alpha < b._alpha; + } + return a < b; + } +}; + //////////////////////////////////////////////////////////////////// // Function: PNMFileTypePNG::Constructor // Access: Public @@ -126,6 +140,7 @@ matches_magic_number(const string &magic_number) const { //////////////////////////////////////////////////////////////////// PNMReader *PNMFileTypePNG:: make_reader(istream *file, bool owns_file, const string &magic_number) { + init_pnm(); return new Reader(this, file, owns_file, magic_number); } @@ -138,8 +153,8 @@ make_reader(istream *file, bool owns_file, const string &magic_number) { //////////////////////////////////////////////////////////////////// PNMWriter *PNMFileTypePNG:: make_writer(ostream *file, bool owns_file) { - // return new Writer(this, file, owns_file); - return NULL; + init_pnm(); + return new Writer(this, file, owns_file); } //////////////////////////////////////////////////////////////////// @@ -358,18 +373,18 @@ read_data(xel *array, xelval *alpha_data) { if (_maxval > 255) { if (get_color) { - red = (source[0] << 16) | source[1]; + red = (source[0] << 8) | source[1]; source += 2; - green = (source[0] << 16) | source[1]; + green = (source[0] << 8) | source[1]; source += 2; } - blue = (source[0] << 16) | source[1]; + blue = (source[0] << 8) | source[1]; source += 2; if (get_alpha) { - alpha = (source[0] << 16) | source[1]; + alpha = (source[0] << 8) | source[1]; source += 2; } @@ -477,5 +492,407 @@ png_error(png_structp png_ptr, png_const_charp error_msg) { longjmp(self->_jmpbuf, true); } +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +PNMFileTypePNG::Writer:: +Writer(PNMFileType *type, ostream *file, bool owns_file) : + PNMWriter(type, file, owns_file) +{ + _png = NULL; + _info = NULL; + _is_valid = false; + + _png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, + png_error, png_warning); + if (_png == NULL) { + return; + } + + _info = png_create_info_struct(_png); + if (_info == NULL) { + png_destroy_write_struct(&_png, NULL); + return; + } + + _is_valid = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::Destructor +// Access: Public, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +PNMFileTypePNG::Writer:: +~Writer() { + free_png(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::write_data +// Access: Public, Virtual +// Description: Writes in an entire image all at once, storing it in +// the pre-allocated _x_size * _y_size array and alpha +// pointers. (If the image type has no alpha channel, +// alpha is ignored.) Returns the number of rows +// correctly write. +// +// Derived classes need not override this if they +// instead provide supports_write_row() and write_row(), +// below. +//////////////////////////////////////////////////////////////////// +int PNMFileTypePNG::Writer:: +write_data(xel *array, xelval *alpha_data) { + if (!is_valid()) { + return 0; + } + + if (setjmp(_jmpbuf)) { + // This is the ANSI C way to handle exceptions. If setjmp(), + // above, returns true, it means that libpng detected an exception + // while executing the code that writes the image, below. + free_png(); + return 0; + } + + png_set_write_fn(_png, (void *)this, png_write_data, png_flush_data); + + // First, write the header. + + int true_bit_depth = pm_maxvaltobits(_maxval); + int png_bit_depth = make_png_bit_depth(true_bit_depth); + + png_color_8 sig_bit; + sig_bit.red = true_bit_depth; + sig_bit.green = true_bit_depth; + sig_bit.blue = true_bit_depth; + sig_bit.gray = true_bit_depth; + sig_bit.alpha = true_bit_depth; + + int color_type = 0; + + if (!is_grayscale()) { + color_type |= PNG_COLOR_MASK_COLOR; + } + if (has_alpha()) { + color_type |= PNG_COLOR_MASK_ALPHA; + } + + // Determine if we should make a palettized image out of this. In + // order for this to be possible and effective, we must have no more + // than 256 unique color/alpha combinations for a color image, and + // the resulting bitdepth should be smaller than what we would have + // otherwise. + Palette palette; + Histogram palette_lookup; + png_color png_palette[png_max_palette]; + png_byte png_trans[png_max_palette]; + + if (png_bit_depth <= 8) { + if (compute_palette(palette, array, alpha_data, png_max_palette)) { + pnmimage_png_cat.debug() + << palette.size() << " colors found.\n"; + + int palette_bit_depth = make_png_bit_depth(pm_maxvaltobits(palette.size() - 1)); + + int total_bits = png_bit_depth; + if (!is_grayscale()) { + total_bits *= 3; + } + if (has_alpha()) { + total_bits += png_bit_depth; + } + + if (palette_bit_depth < total_bits) { + pnmimage_png_cat.debug() + << "palette bit depth of " << palette_bit_depth + << " improves on bit depth of " << total_bits + << "; making a palette image.\n"; + + color_type = PNG_COLOR_TYPE_PALETTE; + + // Re-sort the palette to put the semitransparent pixels at the + // beginning. + sort(palette.begin(), palette.end(), LowAlphaCompare()); + + int num_alpha = 0; + for (int i = 0; i < (int)palette.size(); i++) { + png_palette[i].red = palette[i]._red; + png_palette[i].green = palette[i]._green; + png_palette[i].blue = palette[i]._blue; + png_trans[i] = palette[i]._alpha; + if (palette[i]._alpha != _maxval) { + num_alpha = i + 1; + } + + // Also build a reverse-lookup from color to palette index in + // the "histogram" structure. + palette_lookup[palette[i]] = i; + } + + png_set_PLTE(_png, _info, png_palette, palette.size()); + if (has_alpha()) { + pnmimage_png_cat.debug() + << "palette contains " << num_alpha << " transparent entries.\n"; + png_set_tRNS(_png, _info, png_trans, num_alpha, NULL); + } + } else { + pnmimage_png_cat.debug() + << "palette bit depth of " << palette_bit_depth + << " does not improve on bit depth of " << total_bits + << "; not making a palette image.\n"; + } + + } else { + pnmimage_png_cat.debug() + << "more than " << png_max_palette + << " colors found; not making a palette image.\n"; + } + } else { + pnmimage_png_cat.debug() + << "maxval exceeds 255; not making a palette image.\n"; + } + + + pnmimage_png_cat.debug() + << "width = " << _x_size << " height = " << _y_size + << " maxval = " << _maxval << " bit_depth = " + << png_bit_depth << " color_type = " << color_type << "\n"; + + png_set_IHDR(_png, _info, _x_size, _y_size, png_bit_depth, + color_type, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + // Set the true bit depth of the image data. + if (png_bit_depth != true_bit_depth || color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_sBIT(_png, _info, &sig_bit); + } + + png_write_info(_png, _info); + + + // Now set up the transformations to write the image data. + if (png_bit_depth < 8) { + png_set_packing(_png); + } + + if (png_bit_depth != true_bit_depth && color_type != PNG_COLOR_TYPE_PALETTE) { + // This does assume that _maxval is one less than a power of 2. + // If it is not, the PNG image will be written as if it were the + // next-higher power of 2, darkening the image. + png_set_shift(_png, &sig_bit); + } + + int row_byte_length = _x_size * _num_channels; + if (png_bit_depth > 8) { + row_byte_length *= 2; + } + + int num_rows = _y_size; + + if (pnmimage_png_cat.is_debug()) { + pnmimage_png_cat.debug() + << "Allocating one row of " << row_byte_length + << " bytes.\n"; + } + + // When writing, we only need to copy the image out one row at a + // time, because we don't mess around with writing interlaced files. + // If we were writing an interlaced file, we'd have to copy the + // whole image first. + + png_bytep row = new png_byte[row_byte_length]; + + bool save_color = !is_grayscale(); + bool save_alpha = has_alpha(); + + int pi = 0; + for (int yi = 0; yi < num_rows; yi++) { + png_bytep dest = row; + + if (color_type == PNG_COLOR_TYPE_PALETTE) { + for (int xi = 0; xi < _x_size; xi++) { + int index; + + if (save_color) { + if (save_alpha) { + index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]), alpha_data[pi])]; + } else { + index = palette_lookup[PixelSpec(PPM_GETR(array[pi]), PPM_GETG(array[pi]), PPM_GETB(array[pi]))]; + } + } else { + if (save_alpha) { + index = palette_lookup[PixelSpec(PPM_GETB(array[pi]), alpha_data[pi])]; + } else { + index = palette_lookup[PixelSpec(PPM_GETB(array[pi]))]; + } + } + + *dest++ = index; + pi++; + } + + } else if (png_bit_depth > 255) { + for (int xi = 0; xi < _x_size; xi++) { + if (save_color) { + xelval red = PPM_GETR(array[pi]); + *dest++ = (red >> 8) & 0xff; + *dest++ = red & 0xff; + xelval green = PPM_GETG(array[pi]); + *dest++ = (green >> 8) & 0xff; + *dest++ = green & 0xff; + } + xelval blue = PPM_GETB(array[pi]); + *dest++ = (blue >> 8) & 0xff; + *dest++ = blue & 0xff; + + if (save_alpha) { + xelval alpha = alpha_data[pi]; + *dest++ = (alpha >> 8) & 0xff; + *dest++ = alpha & 0xff; + } + pi++; + } + + } else { + for (int xi = 0; xi < _x_size; xi++) { + if (save_color) { + *dest++ = PPM_GETR(array[pi]); + *dest++ = PPM_GETG(array[pi]); + } + + *dest++ = PPM_GETB(array[pi]); + + if (save_alpha) { + *dest++ = alpha_data[pi]; + } + pi++; + } + } + + nassertr(dest <= row + row_byte_length, yi); + png_write_row(_png, row); + } + + delete[] row; + + png_write_end(_png, NULL); + + return _y_size; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::free_png +// Access: Private +// Description: Releases the internal PNG structures and marks the +// writer invalid. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePNG::Writer:: +free_png() { + if (_is_valid) { + png_destroy_write_struct(&_png, &_info); + _is_valid = false; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::make_png_bit_depth +// Access: Private, Static +// Description: Elevates the indicated bit depth to one of the legal +// PNG bit depths: 1, 2, 4, 8, or 16. +//////////////////////////////////////////////////////////////////// +int PNMFileTypePNG::Writer:: +make_png_bit_depth(int bit_depth) { + switch (bit_depth) { + case 0: + case 1: + return 1; + + case 2: + return 2; + + case 3: + case 4: + return 4; + + case 5: + case 6: + case 7: + case 8: + return 8; + + default: + return 16; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::png_write_data +// Access: Private, Static +// Description: A callback handler that PNG uses to write data from +// the iostream. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePNG::Writer:: +png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { + Writer *self = (Writer *)png_get_io_ptr(png_ptr); + self->_file->write((char *)data, length); + if (self->_file->fail()) { + pnmimage_png_cat.error() + << "Unable to write to the iostream.\n"; + // Is there no way to indicate a write failure to libpng? + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::png_flush_data +// Access: Private, Static +// Description: A callback handler that PNG uses to write data from +// the iostream. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePNG::Writer:: +png_flush_data(png_structp png_ptr) { + Writer *self = (Writer *)png_get_io_ptr(png_ptr); + self->_file->flush(); +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::png_warning +// Access: Private, Static +// Description: This is our own warning handler. It is called by the +// png library to issue a warning message. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePNG::Writer:: +png_warning(png_structp, png_const_charp warning_msg) { + pnmimage_png_cat.warning() + << warning_msg << "\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: PNMFileTypePNG::Writer::png_error +// Access: Private, Static +// Description: This is our own error handler. It is called by the +// png library to issue a fatal error message. +//////////////////////////////////////////////////////////////////// +void PNMFileTypePNG::Writer:: +png_error(png_structp png_ptr, png_const_charp error_msg) { + pnmimage_png_cat.error() + << error_msg << "\n"; + + // The PNG library insists we should not return, so instead of + // returning, we will do a longjmp out of the png code. + Writer *self = (Writer *)png_get_io_ptr(png_ptr); + if (self == (Writer *)NULL) { + // Oops, we haven't got a self pointer yet. Return anyway and + // hope we'll be ok. + pnmimage_png_cat.error() + << "Returning before opening file.\n"; + return; + } + + longjmp(self->_jmpbuf, true); +} + #endif // HAVE_PNG diff --git a/panda/src/pnmimagetypes/pnmFileTypePNG.h b/panda/src/pnmimagetypes/pnmFileTypePNG.h index 96ed844d0c..64b81d7cec 100755 --- a/panda/src/pnmimagetypes/pnmFileTypePNG.h +++ b/panda/src/pnmimagetypes/pnmFileTypePNG.h @@ -75,19 +75,31 @@ public: jmp_buf _jmpbuf; }; - /* class Writer : public PNMWriter { public: Writer(PNMFileType *type, ostream *file, bool owns_file); + virtual ~Writer(); virtual int write_data(xel *array, xelval *alpha); private: + void free_png(); + static int make_png_bit_depth(int bit_depth); static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length); static void png_flush_data(png_structp png_ptr); + + static void png_error(png_structp png_ptr, png_const_charp error_msg); + static void png_warning(png_structp png_ptr, png_const_charp warning_msg); + + png_structp _png; + png_infop _info; + + // We need a jmp_buf to support libpng's fatal error handling, in + // which the error handler must not immediately leave libpng code, + // but must return to the caller in Panda. + jmp_buf _jmpbuf; }; - */ // The TypedWritable interface follows. public: