Jelajahi Sumber

add support for OpenEXR floating-point image files

David Rose 9 tahun lalu
induk
melakukan
403dd39934

+ 10 - 3
makepanda/makepanda.py

@@ -78,7 +78,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
   "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
   "ODE", "PHYSX", "BULLET", "PANDAPHYSICS",            # Physics
   "ODE", "PHYSX", "BULLET", "PANDAPHYSICS",            # Physics
   "SPEEDTREE",                                         # SpeedTree
   "SPEEDTREE",                                         # SpeedTree
-  "ZLIB", "PNG", "JPEG", "TIFF", "SQUISH", "FREETYPE", # 2D Formats support
+  "ZLIB", "PNG", "JPEG", "TIFF", "OPENEXR", "SQUISH", "FREETYPE", # 2D Formats support
   ] + MAYAVERSIONS + MAXVERSIONS + [ "FCOLLADA", "ASSIMP", # 3D Formats support
   ] + MAYAVERSIONS + MAXVERSIONS + [ "FCOLLADA", "ASSIMP", # 3D Formats support
   "VRPN", "OPENSSL",                                   # Transport
   "VRPN", "OPENSSL",                                   # Transport
   "FFTW",                                              # Algorithm helpers
   "FFTW",                                              # Algorithm helpers
@@ -604,6 +604,11 @@ if (COMPILER == "MSVC"):
             LibName("TIFF", GetThirdpartyDir() + "tiff/lib/libtiff.lib")
             LibName("TIFF", GetThirdpartyDir() + "tiff/lib/libtiff.lib")
         else:
         else:
             LibName("TIFF", GetThirdpartyDir() + "tiff/lib/tiff.lib")
             LibName("TIFF", GetThirdpartyDir() + "tiff/lib/tiff.lib")
+    if (PkgSkip("OPENEXR")==0):
+        LibName("OPENEXR",     GetThirdpartyDir() + "openexr/lib/IlmImf-2_2.lib")
+        LibName("OPENEXR",     GetThirdpartyDir() + "openexr/lib/IlmThread-2_2.lib")
+        LibName("OPENEXR",     GetThirdpartyDir() + "openexr/lib/Iex-2_2.lib")
+        LibName("OPENEXR",     GetThirdpartyDir() + "openexr/lib/Half.lib")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
     if (PkgSkip("ZLIB")==0):     LibName("ZLIB",     GetThirdpartyDir() + "zlib/lib/zlibstatic.lib")
     if (PkgSkip("ZLIB")==0):     LibName("ZLIB",     GetThirdpartyDir() + "zlib/lib/zlibstatic.lib")
     if (PkgSkip("VRPN")==0):     LibName("VRPN",     GetThirdpartyDir() + "vrpn/lib/vrpn.lib")
     if (PkgSkip("VRPN")==0):     LibName("VRPN",     GetThirdpartyDir() + "vrpn/lib/vrpn.lib")
@@ -778,6 +783,7 @@ if (COMPILER=="GCC"):
         SmartPkgEnable("OPENAL",    "openal",    ("openal"), "AL/al.h", framework = "OpenAL")
         SmartPkgEnable("OPENAL",    "openal",    ("openal"), "AL/al.h", framework = "OpenAL")
         SmartPkgEnable("SQUISH",    "",          ("squish"), "squish.h")
         SmartPkgEnable("SQUISH",    "",          ("squish"), "squish.h")
         SmartPkgEnable("TIFF",      "libtiff-4", ("tiff"), "tiff.h")
         SmartPkgEnable("TIFF",      "libtiff-4", ("tiff"), "tiff.h")
+        SmartPkgEnable("OPENEXR",   "",          ("openexr"), "ImfOutputFile.h")
         SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
         SmartPkgEnable("VRPN",      "",          ("vrpn", "quat"), ("vrpn", "quat.h", "vrpn/vrpn_Types.h"))
         SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.h"))
         SmartPkgEnable("BULLET", "bullet", ("BulletSoftBody", "BulletDynamics", "BulletCollision", "LinearMath"), ("bullet", "bullet/btBulletDynamicsCommon.h"))
         SmartPkgEnable("VORBIS",    "vorbisfile",("vorbisfile", "vorbis", "ogg"), ("ogg/ogg.h", "vorbis/vorbisfile.h"))
         SmartPkgEnable("VORBIS",    "vorbisfile",("vorbisfile", "vorbis", "ogg"), ("ogg/ogg.h", "vorbis/vorbisfile.h"))
@@ -2201,6 +2207,7 @@ DTOOL_CONFIG=[
     ("PHAVE_JPEGINT_H",                '1',                      '1'),
     ("PHAVE_JPEGINT_H",                '1',                      '1'),
     ("HAVE_VIDEO4LINUX",               'UNDEF',                  '1'),
     ("HAVE_VIDEO4LINUX",               'UNDEF',                  '1'),
     ("HAVE_TIFF",                      'UNDEF',                  'UNDEF'),
     ("HAVE_TIFF",                      'UNDEF',                  'UNDEF'),
+    ("HAVE_OPENEXR",                   'UNDEF',                  'UNDEF'),
     ("HAVE_SGI_RGB",                   '1',                      '1'),
     ("HAVE_SGI_RGB",                   '1',                      '1'),
     ("HAVE_TGA",                       '1',                      '1'),
     ("HAVE_TGA",                       '1',                      '1'),
     ("HAVE_IMG",                       '1',                      '1'),
     ("HAVE_IMG",                       '1',                      '1'),
@@ -3824,7 +3831,7 @@ if (not RUNTIME):
 #
 #
 
 
 if (not RUNTIME):
 if (not RUNTIME):
-  OPTS=['DIR:panda/src/pnmimagetypes', 'DIR:panda/src/pnmimage', 'BUILDING:PANDA', 'PNG', 'ZLIB', 'JPEG', 'TIFF']
+  OPTS=['DIR:panda/src/pnmimagetypes', 'DIR:panda/src/pnmimage', 'BUILDING:PANDA', 'PNG', 'ZLIB', 'JPEG', 'TIFF', 'OPENEXR']
   TargetAdd('p3pnmimagetypes_composite1.obj', opts=OPTS, input='p3pnmimagetypes_composite1.cxx')
   TargetAdd('p3pnmimagetypes_composite1.obj', opts=OPTS, input='p3pnmimagetypes_composite1.cxx')
   TargetAdd('p3pnmimagetypes_composite2.obj', opts=OPTS, input='p3pnmimagetypes_composite2.cxx')
   TargetAdd('p3pnmimagetypes_composite2.obj', opts=OPTS, input='p3pnmimagetypes_composite2.cxx')
 
 
@@ -3869,7 +3876,7 @@ if (not RUNTIME):
 
 
 if (not RUNTIME):
 if (not RUNTIME):
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG',
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG',
-      'TIFF', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
+      'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
       'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
       'SQUISH', 'NVIDIACG', 'VORBIS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
 
 
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')

+ 11 - 0
panda/src/pnmimagetypes/config_pnmimagetypes.cxx

@@ -22,6 +22,7 @@
 #include "pnmFileTypePNM.h"
 #include "pnmFileTypePNM.h"
 #include "pnmFileTypePfm.h"
 #include "pnmFileTypePfm.h"
 #include "pnmFileTypeTIFF.h"
 #include "pnmFileTypeTIFF.h"
+#include "pnmFileTypeEXR.h"
 #include "pnmFileTypeStbImage.h"
 #include "pnmFileTypeStbImage.h"
 #include "sgi.h"
 #include "sgi.h"
 
 
@@ -41,6 +42,7 @@ NotifyCategoryDefName(pnmimage_jpg, "jpg", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_png, "png", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_png, "png", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_pnm, "pnm", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_pnm, "pnm", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_tiff, "tiff", pnmimage_cat);
 NotifyCategoryDefName(pnmimage_tiff, "tiff", pnmimage_cat);
+NotifyCategoryDefName(pnmimage_exr, "exr", pnmimage_cat);
 
 
 ConfigVariableEnum<SGIStorageType> sgi_storage_type
 ConfigVariableEnum<SGIStorageType> sgi_storage_type
 ("sgi-storage-type", SST_rle,
 ("sgi-storage-type", SST_rle,
@@ -241,6 +243,12 @@ init_libpnmimagetypes() {
   tr->register_type(new PNMFileTypeTIFF);
   tr->register_type(new PNMFileTypeTIFF);
 #endif
 #endif
 
 
+#ifdef HAVE_OPENEXR
+  PNMFileTypeEXR::init_type();
+  PNMFileTypeEXR::register_with_read_factory();
+  tr->register_type(new PNMFileTypeEXR);
+#endif
+
 #ifdef HAVE_STB_IMAGE
 #ifdef HAVE_STB_IMAGE
   PNMFileTypeStbImage::init_type();
   PNMFileTypeStbImage::init_type();
   PNMFileTypeStbImage::register_with_read_factory();
   PNMFileTypeStbImage::register_with_read_factory();
@@ -259,4 +267,7 @@ init_libpnmimagetypes() {
 #ifdef HAVE_TIFF
 #ifdef HAVE_TIFF
   ps->add_system("libtiff");
   ps->add_system("libtiff");
 #endif
 #endif
+#ifdef HAVE_OPENEXR
+  ps->add_system("openexr");
+#endif
 }
 }

+ 1 - 0
panda/src/pnmimagetypes/config_pnmimagetypes.h

@@ -32,6 +32,7 @@
 
 
 NotifyCategoryDecl(pnmimage_sgi, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_sgi, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_tiff, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_tiff, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
+NotifyCategoryDecl(pnmimage_exr, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_tga, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_tga, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_img, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_img, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_soft, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);
 NotifyCategoryDecl(pnmimage_soft, EXPCL_PANDA_PNMIMAGETYPES, EXPTP_PANDA_PNMIMAGETYPES);

+ 2 - 1
panda/src/pnmimagetypes/p3pnmimagetypes_composite1.cxx

@@ -1,8 +1,9 @@
 #include "config_pnmimagetypes.cxx"
 #include "config_pnmimagetypes.cxx"
 #include "pnmFileTypeBMPReader.cxx"
 #include "pnmFileTypeBMPReader.cxx"
 #include "pnmFileTypeBMPWriter.cxx"
 #include "pnmFileTypeBMPWriter.cxx"
-#include "pnmFileTypeIMG.cxx"
 #include "pnmFileTypeBMP.cxx"
 #include "pnmFileTypeBMP.cxx"
+#include "pnmFileTypeEXR.cxx"
+#include "pnmFileTypeIMG.cxx"
 #include "pnmFileTypeJPG.cxx"
 #include "pnmFileTypeJPG.cxx"
 #include "pnmFileTypeJPGReader.cxx"
 #include "pnmFileTypeJPGReader.cxx"
 #include "pnmFileTypeJPGWriter.cxx"
 #include "pnmFileTypeJPGWriter.cxx"

+ 470 - 0
panda/src/pnmimagetypes/pnmFileTypeEXR.cxx

@@ -0,0 +1,470 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pnmFileTypeEXR.cxx
+ * @author drose
+ * @date 2000-06-19
+ */
+
+#include "pnmFileTypeEXR.h"
+
+#ifdef HAVE_OPENEXR
+
+#include "config_pnmimagetypes.h"
+
+#include "pnmFileTypeRegistry.h"
+#include "bamReader.h"
+#include "pfmFile.h"
+
+#include <ImfOutputFile.h>
+#include <ImfChannelList.h>
+#include <ImfVersion.h>
+
+TypeHandle PNMFileTypeEXR::_type_handle;
+
+static const char * const extensions_exr[] = {
+  "exr"
+};
+static const int num_extensions_exr = sizeof(extensions_exr) / sizeof(const char *);
+
+// A wrapper class to map OpenEXR's OStream class onto std::ostream.
+class ImfStdOstream : public IMF::OStream {
+public:
+  ImfStdOstream(std::ostream &strm) : IMF::OStream("ostream"), _strm(strm) {}
+
+  virtual void write(const char c[/*n*/], int n) {
+    _strm.write(c, n);
+  }
+
+  virtual IMF::Int64 tellp() {
+    return _strm.tellp();
+  }
+
+  virtual void seekp(IMF::Int64 pos) {
+    _strm.seekp(pos);
+  }
+
+private:
+  std::ostream &_strm;
+};
+
+// A wrapper class to map OpenEXR's IStream class onto std::istream.
+class ImfStdIstream : public IMF::IStream {
+public:
+  ImfStdIstream(std::istream &strm, const std::string &magic_number) : IMF::IStream("istream"), _strm(strm) {
+    // Start by putting back the magic number.
+    for (std::string::const_reverse_iterator mi = magic_number.rbegin();
+         mi != magic_number.rend();
+         mi++) {
+      _strm.putback(*mi);
+    }
+  }
+
+  virtual bool isMemoryMapped () const {
+    return false;
+  }
+
+  virtual bool read (char c[/*n*/], int n) {
+    _strm.read(c, n);
+    if (_strm.gcount() != n) {
+      throw std::exception();
+    }
+
+    bool not_eof = !_strm.eof();
+    return not_eof;
+  }
+
+  virtual IMF::Int64 tellg() {
+    return _strm.tellg();
+  }
+
+  virtual void seekg(IMF::Int64 pos) {
+    _strm.seekg(pos);
+  }
+
+  virtual void clear() {
+    _strm.clear();
+  }
+
+private:
+  std::istream &_strm;
+};
+
+PNMFileTypeEXR::
+PNMFileTypeEXR() {
+}
+
+/**
+ * Returns a few words describing the file type.
+ */
+string PNMFileTypeEXR::
+get_name() const {
+  return "OpenEXR";
+}
+
+/**
+ * Returns the number of different possible filename extensions associated
+ * with this particular file type.
+ */
+int PNMFileTypeEXR::
+get_num_extensions() const {
+  return num_extensions_exr;
+}
+
+/**
+ * Returns the nth possible filename extension associated with this particular
+ * file type, without a leading dot.
+ */
+string PNMFileTypeEXR::
+get_extension(int n) const {
+  nassertr(n >= 0 && n < num_extensions_exr, string());
+  return extensions_exr[n];
+}
+
+/**
+ * Returns a suitable filename extension (without a leading dot) to suggest
+ * for files of this type, or empty string if no suggestions are available.
+ */
+string PNMFileTypeEXR::
+get_suggested_extension() const {
+  return "exr";
+}
+
+/**
+ * Returns true if this particular file type uses a magic number to identify
+ * it, false otherwise.
+ */
+bool PNMFileTypeEXR::
+has_magic_number() const {
+  return true;
+}
+
+/**
+ * Returns true if the indicated "magic number" byte stream (the initial few
+ * bytes read from the file) matches this particular file type, false
+ * otherwise.
+ */
+bool PNMFileTypeEXR::
+matches_magic_number(const string &magic_number) const {
+  nassertr(magic_number.size() >= 2, false);
+
+  if (magic_number.size() >= 4) {
+    // If we have already read all four bytes, use the built-in
+    // function to check them.
+    return IMF::isImfMagic(magic_number.data());
+  } else {
+    // Otherwise, check only the first two bytes and call it good enough.
+    return magic_number[0] == ((IMF::MAGIC >>  0) & 0x00ff) &&
+      magic_number[1] == ((IMF::MAGIC >>  8) & 0x00ff);
+  }
+}
+
+/**
+ * Allocates and returns a new PNMReader suitable for reading from this file
+ * type, if possible.  If reading from this file type is not supported,
+ * returns NULL.
+ */
+PNMReader *PNMFileTypeEXR::
+make_reader(istream *file, bool owns_file, const string &magic_number) {
+  init_pnm();
+  return new Reader(this, file, owns_file, magic_number);
+}
+
+/**
+ * Allocates and returns a new PNMWriter suitable for reading from this file
+ * type, if possible.  If writing files of this type is not supported, returns
+ * NULL.
+ */
+PNMWriter *PNMFileTypeEXR::
+make_writer(ostream *file, bool owns_file) {
+  init_pnm();
+  return new Writer(this, file, owns_file);
+}
+
+/**
+ *
+ */
+PNMFileTypeEXR::Reader::
+Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
+  PNMReader(type, file, owns_file),
+  _strm(new ImfStdIstream(*_file, magic_number)),
+  _imf_file(*_strm)
+{
+  const IMF::Header &header = _imf_file.header();
+
+  IMATH_NAMESPACE::Box2i dw = header.dataWindow();
+  _x_size = dw.max.x - dw.min.x + 1;
+  _y_size = dw.max.y - dw.min.y + 1;
+
+  // Find the channels we care about, and ensure they're placed in the
+  // correct order.
+  _channel_names.clear();
+
+  const IMF::ChannelList &channels = header.channels();
+
+  // Note: including Y in this list allows us to handle grayscale or
+  // grayscale/alpha images correctly, but also incorrectly detects
+  // luminance/chroma images as grayscale only.  However, these kind
+  // of images are a pain to handle anyway, so maybe that's OK.
+  const char *possible_channel_names[] = { "R", "G", "B", "Y", "A", NULL };
+  for (const char **pni = possible_channel_names; *pni != NULL; ++pni) {
+    std::string name = *pni;
+    IMF::ChannelList::ConstIterator ci = channels.find(name);
+    if (ci != channels.end()) {
+      // Found a match.
+      if (name == "Y" && !_channel_names.empty()) {
+        // Y is luminance or grayscale.  Ignore Y if there are
+        // already any RGB channels.
+      } else {
+        _channel_names.push_back(name);
+      }
+    }
+  }
+
+  if (_channel_names.empty()) {
+    // Didn't find any channel names that match R, G, B, A, so just
+    // ask for RGB anyway and trust the OpenEXR library to do the
+    // right thing.  Actually, it just fills them with black, but
+    // whatever.
+    _channel_names.push_back("R");
+    _channel_names.push_back("G");
+    _channel_names.push_back("B");
+  }
+
+  _num_channels = (int)_channel_names.size();
+  if (_num_channels == 0 || _num_channels > 4) {
+    _is_valid = false;
+    return;
+  }
+  // We read all OpenEXR files to floating-point, even UINT type, so
+  // _maxval doesn't matter.  But we set it anyway.
+  _maxval = 65535;
+
+  _is_valid = true;
+}
+
+/**
+ *
+ */
+PNMFileTypeEXR::Reader::
+~Reader() {
+  delete _strm;
+}
+
+/**
+ * Returns true if this PNMFileType represents a floating-point image type,
+ * false if it is a normal, integer type.  If this returns true, read_pfm() is
+ * implemented instead of read_data().
+ */
+bool PNMFileTypeEXR::Reader::
+is_floating_point() {
+  // We read everything to floating-point, since even the UINT type is
+  // 32 bits, more fidelity than we can represent in our 16-bit
+  // PNMImage.
+  return true;
+}
+
+/**
+ * Reads floating-point data directly into the indicated PfmFile.  Returns
+ * true on success, false on failure.
+ */
+bool PNMFileTypeEXR::Reader::
+read_pfm(PfmFile &pfm) {
+  pfm.clear(_x_size, _y_size, _num_channels);
+  vector_float table;
+  pfm.swap_table(table);
+
+  PN_float32 *table_data = table.data();
+  size_t x_stride = sizeof(PN_float32) * pfm.get_num_channels();
+  size_t y_stride = x_stride * pfm.get_x_size();
+  nassertr(y_stride * pfm.get_y_size() <= table.size() * sizeof(PN_float32), false);
+
+  const IMF::Header &header = _imf_file.header();
+  IMATH_NAMESPACE::Box2i dw = header.dataWindow();
+
+  IMF::FrameBuffer frameBuffer;
+  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
+    char *base = (char *)(table_data - (dw.min.x + dw.min.y * pfm.get_x_size()) * pfm.get_num_channels() + ci);
+    frameBuffer.insert(_channel_names[ci].c_str(),
+                       IMF::Slice(IMF::FLOAT, base, x_stride, y_stride,
+                                  1, 1, 0.0));
+  }
+
+  _imf_file.setFrameBuffer(frameBuffer);
+
+  try {
+    _imf_file.readPixels(dw.min.y, dw.max.y);
+  } catch (const std::exception &exc) {
+    pnmimage_exr_cat.error()
+      << exc.what() << "\n";
+    return false;
+  }
+
+  pfm.swap_table(table);
+  return true;
+}
+
+/**
+ * Reads 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
+ * read.
+ *
+ * Derived classes need not override this if they instead provide
+ * supports_read_row() and read_row(), below.
+ */
+int PNMFileTypeEXR::Reader::
+read_data(xel *array, xelval *alpha) {
+  // This should never come here, since we always read to
+  // floating-point data.
+  nassertr(false, 0);
+  return 0;
+}
+
+/**
+ *
+ */
+PNMFileTypeEXR::Writer::
+Writer(PNMFileType *type, ostream *file, bool owns_file) :
+  PNMWriter(type, file, owns_file)
+{
+}
+
+/**
+ * Returns true if this PNMFileType can accept a floating-point image type,
+ * false if it can only accept a normal, integer type.  If this returns true,
+ * write_pfm() is implemented.
+ */
+bool PNMFileTypeEXR::Writer::
+supports_floating_point() {
+  return true;
+}
+
+/**
+ * Returns true if this PNMFileType can accept an integer image type, false if
+ * it can only accept a floating-point type.  If this returns true,
+ * write_data() or write_row() is implemented.
+ */
+bool PNMFileTypeEXR::Writer::
+supports_integer() {
+  return false;
+}
+
+/**
+ * Writes floating-point data from the indicated PfmFile.  Returns true on
+ * success, false on failure.
+ */
+bool PNMFileTypeEXR::Writer::
+write_pfm(const PfmFile &pfm) {
+  const vector_float &table = pfm.get_table();
+  const PN_float32 *table_data = table.data();
+  size_t x_stride = sizeof(PN_float32) * pfm.get_num_channels();
+  size_t y_stride = x_stride * pfm.get_x_size();
+  nassertr(y_stride * pfm.get_y_size() <= table.size() * sizeof(PN_float32), false);
+
+  const char *channel_names_1[] = { "G" };
+  const char *channel_names_2[] = { "G", "A" };
+  const char *channel_names_3[] = { "R", "G", "B" };
+  const char *channel_names_4[] = { "R", "G", "B", "A" };
+  const char **channel_names = NULL;
+
+  switch (pfm.get_num_channels()) {
+  case 1:
+    channel_names = channel_names_1;
+    break;
+
+  case 2:
+    channel_names = channel_names_2;
+    break;
+
+  case 3:
+    channel_names = channel_names_3;
+    break;
+
+  case 4:
+    channel_names = channel_names_4;
+    break;
+
+  default:
+    return false;
+  };
+
+  IMF::Header header(pfm.get_x_size(), pfm.get_y_size());
+  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
+    header.channels().insert(channel_names[ci], IMF::Channel(IMF::FLOAT));
+  }
+
+  IMF::FrameBuffer frameBuffer;
+  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
+    const char *base = (const char *)(table_data + ci);
+    frameBuffer.insert(channel_names[ci],
+                       IMF::Slice(IMF::FLOAT, (char *)base, x_stride, y_stride));
+  }
+
+  ImfStdOstream strm(*_file);
+  IMF::OutputFile file(strm, header);
+  file.setFrameBuffer(frameBuffer);
+
+  try {
+    file.writePixels(pfm.get_y_size());
+  } catch (const std::exception &exc) {
+    pnmimage_exr_cat.error()
+      << exc.what() << "\n";
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Writes out an entire image all at once, including the header, based on the
+ * image data stored in the given _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 written.
+ *
+ * It is the user's responsibility to fill in the header data via calls to
+ * set_x_size(), set_num_channels(), etc., or copy_header_from(), before
+ * calling write_data().
+ *
+ * It is important to delete the PNMWriter class after successfully writing
+ * the data.  Failing to do this may result in some data not getting flushed!
+ *
+ * Derived classes need not override this if they instead provide
+ * supports_streaming() and write_row(), below.
+ */
+int PNMFileTypeEXR::Writer::
+write_data(xel *array, xelval *alpha) {
+  // This should never come here, since we always write to
+  // floating-point data.
+  nassertr(false, 0);
+  return 0;
+}
+
+/**
+ * Registers the current object as something that can be read from a Bam file.
+ */
+void PNMFileTypeEXR::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_PNMFileTypeEXR);
+}
+
+/**
+ * This method is called by the BamReader when an object of this type is
+ * encountered in a Bam file; it should allocate and return a new object with
+ * all the data read.
+ *
+ * In the case of the PNMFileType objects, since these objects are all shared,
+ * we just pull the object from the registry.
+ */
+TypedWritable *PNMFileTypeEXR::
+make_PNMFileTypeEXR(const FactoryParams &params) {
+  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
+}
+
+#endif  // HAVE_OPENEXR

+ 110 - 0
panda/src/pnmimagetypes/pnmFileTypeEXR.h

@@ -0,0 +1,110 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file pnmFileTypeEXR.h
+ * @author drose
+ * @date 2000-06-17
+ */
+
+#ifndef PNMFILETYPEEXR_H
+#define PNMFILETYPEEXR_H
+
+#include "pandabase.h"
+
+#ifdef HAVE_OPENEXR
+
+#include "pnmFileType.h"
+#include "pnmReader.h"
+#include "pnmWriter.h"
+
+#include <ImfInputFile.h>
+#include <ImfNamespace.h>
+
+namespace IMF = OPENEXR_IMF_NAMESPACE;
+
+class ImfStdIstream;
+
+/**
+ * For reading and writing EXR floating-point or integer files.
+ */
+class EXPCL_PANDA_PNMIMAGETYPES PNMFileTypeEXR : public PNMFileType {
+public:
+  PNMFileTypeEXR();
+
+  virtual string get_name() const;
+
+  virtual int get_num_extensions() const;
+  virtual string get_extension(int n) const;
+  virtual string get_suggested_extension() const;
+
+  virtual bool has_magic_number() const;
+  virtual bool matches_magic_number(const string &magic_number) const;
+
+  virtual PNMReader *make_reader(istream *file, bool owns_file = true,
+                                 const string &magic_number = string());
+  virtual PNMWriter *make_writer(ostream *file, bool owns_file = true);
+
+public:
+  class Reader : public PNMReader {
+  public:
+    Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number);
+    virtual ~Reader();
+
+    virtual bool is_floating_point();
+    virtual bool read_pfm(PfmFile &pfm);
+    virtual int read_data(xel *array, xelval *alpha);
+
+  private:
+    class ImfStdIstream *_strm;
+    IMF::InputFile _imf_file;
+
+    typedef std::vector<std::string> ChannelNames;
+    ChannelNames _channel_names;
+    IMF::PixelType _best_pixel_type;
+  };
+
+  class Writer : public PNMWriter {
+  public:
+    Writer(PNMFileType *type, ostream *file, bool owns_file);
+
+    virtual bool supports_floating_point();
+    virtual bool supports_integer();
+    virtual bool write_pfm(const PfmFile &pfm);
+    virtual int write_data(xel *array, xelval *alpha);
+  };
+
+private:
+
+  // The TypedWritable interface follows.
+public:
+  static void register_with_read_factory();
+
+protected:
+  static TypedWritable *make_PNMFileTypeEXR(const FactoryParams &params);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PNMFileType::init_type();
+    register_type(_type_handle, "PNMFileTypeEXR",
+                  PNMFileType::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif  // HAVE_OPENEXR
+
+#endif