2
0
Эх сурвалжийг харах

support writing png images

David Rose 22 жил өмнө
parent
commit
a2772d745a

+ 424 - 7
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

+ 14 - 2
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: