|
|
@@ -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
|