Panda3D
pnmFileTypeEXR.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 pnmFileTypeEXR.cxx
10  * @author drose
11  * @date 2000-06-19
12  */
13 
14 #include "pnmFileTypeEXR.h"
15 
16 #ifdef HAVE_OPENEXR
17 
18 #include "config_pnmimagetypes.h"
19 
20 #include "pnmFileTypeRegistry.h"
21 #include "bamReader.h"
22 #include "pfmFile.h"
23 
24 #include <ImfOutputFile.h>
25 #include <ImfChannelList.h>
26 #include <ImfVersion.h>
27 #include <ImfIO.h>
28 
29 #ifndef IMATH_NAMESPACE
30 #define IMATH_NAMESPACE Imath
31 #endif
32 
33 using std::istream;
34 using std::ostream;
35 using std::string;
36 
37 TypeHandle PNMFileTypeEXR::_type_handle;
38 
39 static const char * const extensions_exr[] = {
40  "exr"
41 };
42 static const int num_extensions_exr = sizeof(extensions_exr) / sizeof(const char *);
43 
44 // A wrapper class to map OpenEXR's OStream class onto std::ostream.
45 class ImfStdOstream : public IMF::OStream {
46 public:
47  ImfStdOstream(std::ostream &strm) : IMF::OStream("ostream"), _strm(strm) {}
48 
49  virtual void write(const char c[/*n*/], int n) {
50  _strm.write(c, n);
51  }
52 
53  virtual IMF::Int64 tellp() {
54  return _strm.tellp();
55  }
56 
57  virtual void seekp(IMF::Int64 pos) {
58  _strm.seekp(pos);
59  }
60 
61 private:
62  std::ostream &_strm;
63 };
64 
65 // A wrapper class to map OpenEXR's IStream class onto std::istream.
66 class ImfStdIstream : public IMF::IStream {
67 public:
68  ImfStdIstream(std::istream &strm, const std::string &magic_number) : IMF::IStream("istream"), _strm(strm) {
69  // Start by putting back the magic number.
70  for (std::string::const_reverse_iterator mi = magic_number.rbegin();
71  mi != magic_number.rend();
72  mi++) {
73  _strm.putback(*mi);
74  }
75  }
76 
77  virtual bool isMemoryMapped () const {
78  return false;
79  }
80 
81  virtual bool read (char c[/*n*/], int n) {
82  _strm.read(c, n);
83  if (_strm.gcount() != n) {
84  throw std::exception();
85  }
86 
87  bool not_eof = !_strm.eof();
88  return not_eof;
89  }
90 
91  virtual IMF::Int64 tellg() {
92  return _strm.tellg();
93  }
94 
95  virtual void seekg(IMF::Int64 pos) {
96  _strm.seekg(pos);
97  }
98 
99  virtual void clear() {
100  _strm.clear();
101  }
102 
103 private:
104  std::istream &_strm;
105 };
106 
107 PNMFileTypeEXR::
108 PNMFileTypeEXR() {
109 }
110 
111 /**
112  * Returns a few words describing the file type.
113  */
114 string PNMFileTypeEXR::
115 get_name() const {
116  return "OpenEXR";
117 }
118 
119 /**
120  * Returns the number of different possible filename extensions associated
121  * with this particular file type.
122  */
123 int PNMFileTypeEXR::
124 get_num_extensions() const {
125  return num_extensions_exr;
126 }
127 
128 /**
129  * Returns the nth possible filename extension associated with this particular
130  * file type, without a leading dot.
131  */
132 string PNMFileTypeEXR::
133 get_extension(int n) const {
134  nassertr(n >= 0 && n < num_extensions_exr, string());
135  return extensions_exr[n];
136 }
137 
138 /**
139  * Returns a suitable filename extension (without a leading dot) to suggest
140  * for files of this type, or empty string if no suggestions are available.
141  */
142 string PNMFileTypeEXR::
143 get_suggested_extension() const {
144  return "exr";
145 }
146 
147 /**
148  * Returns true if this particular file type uses a magic number to identify
149  * it, false otherwise.
150  */
151 bool PNMFileTypeEXR::
152 has_magic_number() const {
153  return true;
154 }
155 
156 /**
157  * Returns true if the indicated "magic number" byte stream (the initial few
158  * bytes read from the file) matches this particular file type, false
159  * otherwise.
160  */
161 bool PNMFileTypeEXR::
162 matches_magic_number(const string &magic_number) const {
163  nassertr(magic_number.size() >= 2, false);
164 
165  if (magic_number.size() >= 4) {
166  // If we have already read all four bytes, use the built-in
167  // function to check them.
168  return IMF::isImfMagic(magic_number.data());
169  } else {
170  // Otherwise, check only the first two bytes and call it good enough.
171  return magic_number[0] == ((IMF::MAGIC >> 0) & 0x00ff) &&
172  magic_number[1] == ((IMF::MAGIC >> 8) & 0x00ff);
173  }
174 }
175 
176 /**
177  * Allocates and returns a new PNMReader suitable for reading from this file
178  * type, if possible. If reading from this file type is not supported,
179  * returns NULL.
180  */
181 PNMReader *PNMFileTypeEXR::
182 make_reader(istream *file, bool owns_file, const string &magic_number) {
183  init_pnm();
184  return new Reader(this, file, owns_file, magic_number);
185 }
186 
187 /**
188  * Allocates and returns a new PNMWriter suitable for reading from this file
189  * type, if possible. If writing files of this type is not supported, returns
190  * NULL.
191  */
192 PNMWriter *PNMFileTypeEXR::
193 make_writer(ostream *file, bool owns_file) {
194  init_pnm();
195  return new Writer(this, file, owns_file);
196 }
197 
198 /**
199  *
200  */
201 PNMFileTypeEXR::Reader::
202 Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
203  PNMReader(type, file, owns_file),
204  _strm(new ImfStdIstream(*_file, magic_number)),
205  _imf_file(*_strm)
206 {
207  const IMF::Header &header = _imf_file.header();
208 
209  IMATH_NAMESPACE::Box2i dw = header.dataWindow();
210  _x_size = dw.max.x - dw.min.x + 1;
211  _y_size = dw.max.y - dw.min.y + 1;
212 
213  // Find the channels we care about, and ensure they're placed in the
214  // correct order.
215  _channel_names.clear();
216 
217  const IMF::ChannelList &channels = header.channels();
218 
219  // Note: including Y in this list allows us to handle grayscale or
220  // grayscale/alpha images correctly, but also incorrectly detects
221  // luminance/chroma images as grayscale only. However, these kind
222  // of images are a pain to handle anyway, so maybe that's OK.
223  const char *possible_channel_names[] = { "R", "G", "B", "Y", "A", nullptr };
224  for (const char **pni = possible_channel_names; *pni != nullptr; ++pni) {
225  std::string name = *pni;
226  IMF::ChannelList::ConstIterator ci = channels.find(name.c_str());
227  if (ci != channels.end()) {
228  // Found a match.
229  if (name == "Y" && !_channel_names.empty()) {
230  // Y is luminance or grayscale. Ignore Y if there are
231  // already any RGB channels.
232  } else {
233  _channel_names.push_back(name);
234  }
235  }
236  }
237 
238  if (_channel_names.empty()) {
239  // Didn't find any channel names that match R, G, B, A, so just
240  // ask for RGB anyway and trust the OpenEXR library to do the
241  // right thing. Actually, it just fills them with black, but
242  // whatever.
243  _channel_names.push_back("R");
244  _channel_names.push_back("G");
245  _channel_names.push_back("B");
246  }
247 
248  _num_channels = (int)_channel_names.size();
249  if (_num_channels == 0 || _num_channels > 4) {
250  _is_valid = false;
251  return;
252  }
253  // We read all OpenEXR files to floating-point, even UINT type, so
254  // _maxval doesn't matter. But we set it anyway.
255  _maxval = 65535;
256 
257  _is_valid = true;
258 }
259 
260 /**
261  *
262  */
263 PNMFileTypeEXR::Reader::
264 ~Reader() {
265  delete _strm;
266 }
267 
268 /**
269  * Returns true if this PNMFileType represents a floating-point image type,
270  * false if it is a normal, integer type. If this returns true, read_pfm() is
271  * implemented instead of read_data().
272  */
273 bool PNMFileTypeEXR::Reader::
274 is_floating_point() {
275  // We read everything to floating-point, since even the UINT type is
276  // 32 bits, more fidelity than we can represent in our 16-bit
277  // PNMImage.
278  return true;
279 }
280 
281 /**
282  * Reads floating-point data directly into the indicated PfmFile. Returns
283  * true on success, false on failure.
284  */
285 bool PNMFileTypeEXR::Reader::
286 read_pfm(PfmFile &pfm) {
287  pfm.clear(_x_size, _y_size, _num_channels);
288  vector_float table;
289  pfm.swap_table(table);
290 
291  PN_float32 *table_data = table.data();
292  size_t x_stride = sizeof(PN_float32) * pfm.get_num_channels();
293  size_t y_stride = x_stride * pfm.get_x_size();
294  nassertr(y_stride * pfm.get_y_size() <= table.size() * sizeof(PN_float32), false);
295 
296  const IMF::Header &header = _imf_file.header();
297  IMATH_NAMESPACE::Box2i dw = header.dataWindow();
298 
299  IMF::FrameBuffer frameBuffer;
300  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
301  char *base = (char *)(table_data - (dw.min.x + dw.min.y * pfm.get_x_size()) * pfm.get_num_channels() + ci);
302  frameBuffer.insert(_channel_names[ci].c_str(),
303  IMF::Slice(IMF::FLOAT, base, x_stride, y_stride,
304  1, 1, 0.0));
305  }
306 
307  _imf_file.setFrameBuffer(frameBuffer);
308 
309  try {
310  _imf_file.readPixels(dw.min.y, dw.max.y);
311  } catch (const std::exception &exc) {
312  pnmimage_exr_cat.error()
313  << exc.what() << "\n";
314  return false;
315  }
316 
317  pfm.swap_table(table);
318  return true;
319 }
320 
321 /**
322  * Reads in an entire image all at once, storing it in the pre-allocated
323  * _x_size * _y_size array and alpha pointers. (If the image type has no
324  * alpha channel, alpha is ignored.) Returns the number of rows correctly
325  * read.
326  *
327  * Derived classes need not override this if they instead provide
328  * supports_read_row() and read_row(), below.
329  */
330 int PNMFileTypeEXR::Reader::
331 read_data(xel *array, xelval *alpha) {
332  // This should never come here, since we always read to
333  // floating-point data.
334  nassertr(false, 0);
335  return 0;
336 }
337 
338 /**
339  *
340  */
341 PNMFileTypeEXR::Writer::
342 Writer(PNMFileType *type, ostream *file, bool owns_file) :
343  PNMWriter(type, file, owns_file)
344 {
345 }
346 
347 /**
348  * Returns true if this PNMFileType can accept a floating-point image type,
349  * false if it can only accept a normal, integer type. If this returns true,
350  * write_pfm() is implemented.
351  */
352 bool PNMFileTypeEXR::Writer::
353 supports_floating_point() {
354  return true;
355 }
356 
357 /**
358  * Returns true if this PNMFileType can accept an integer image type, false if
359  * it can only accept a floating-point type. If this returns true,
360  * write_data() or write_row() is implemented.
361  */
362 bool PNMFileTypeEXR::Writer::
363 supports_integer() {
364  return false;
365 }
366 
367 /**
368  * Writes floating-point data from the indicated PfmFile. Returns true on
369  * success, false on failure.
370  */
371 bool PNMFileTypeEXR::Writer::
372 write_pfm(const PfmFile &pfm) {
373  const vector_float &table = pfm.get_table();
374  const PN_float32 *table_data = table.data();
375  size_t x_stride = sizeof(PN_float32) * pfm.get_num_channels();
376  size_t y_stride = x_stride * pfm.get_x_size();
377  nassertr(y_stride * pfm.get_y_size() <= table.size() * sizeof(PN_float32), false);
378 
379  const char *channel_names_1[] = { "G" };
380  const char *channel_names_2[] = { "G", "A" };
381  const char *channel_names_3[] = { "R", "G", "B" };
382  const char *channel_names_4[] = { "R", "G", "B", "A" };
383  const char **channel_names = nullptr;
384 
385  switch (pfm.get_num_channels()) {
386  case 1:
387  channel_names = channel_names_1;
388  break;
389 
390  case 2:
391  channel_names = channel_names_2;
392  break;
393 
394  case 3:
395  channel_names = channel_names_3;
396  break;
397 
398  case 4:
399  channel_names = channel_names_4;
400  break;
401 
402  default:
403  return false;
404  };
405 
406  IMF::Header header(pfm.get_x_size(), pfm.get_y_size());
407  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
408  header.channels().insert(channel_names[ci], IMF::Channel(IMF::FLOAT));
409  }
410 
411  IMF::FrameBuffer frameBuffer;
412  for (int ci = 0; ci < pfm.get_num_channels(); ++ci) {
413  const char *base = (const char *)(table_data + ci);
414  frameBuffer.insert(channel_names[ci],
415  IMF::Slice(IMF::FLOAT, (char *)base, x_stride, y_stride));
416  }
417 
418  ImfStdOstream strm(*_file);
419  IMF::OutputFile file(strm, header);
420  file.setFrameBuffer(frameBuffer);
421 
422  try {
423  file.writePixels(pfm.get_y_size());
424  } catch (const std::exception &exc) {
425  pnmimage_exr_cat.error()
426  << exc.what() << "\n";
427  return false;
428  }
429 
430  return true;
431 }
432 
433 /**
434  * Writes out an entire image all at once, including the header, based on the
435  * image data stored in the given _x_size * _y_size array and alpha pointers.
436  * (If the image type has no alpha channel, alpha is ignored.) Returns the
437  * number of rows correctly written.
438  *
439  * It is the user's responsibility to fill in the header data via calls to
440  * set_x_size(), set_num_channels(), etc., or copy_header_from(), before
441  * calling write_data().
442  *
443  * It is important to delete the PNMWriter class after successfully writing
444  * the data. Failing to do this may result in some data not getting flushed!
445  *
446  * Derived classes need not override this if they instead provide
447  * supports_streaming() and write_row(), below.
448  */
449 int PNMFileTypeEXR::Writer::
450 write_data(xel *array, xelval *alpha) {
451  // This should never come here, since we always write to
452  // floating-point data.
453  nassertr(false, 0);
454  return 0;
455 }
456 
457 /**
458  * Registers the current object as something that can be read from a Bam file.
459  */
460 void PNMFileTypeEXR::
461 register_with_read_factory() {
463  register_factory(get_class_type(), make_PNMFileTypeEXR);
464 }
465 
466 /**
467  * This method is called by the BamReader when an object of this type is
468  * encountered in a Bam file; it should allocate and return a new object with
469  * all the data read.
470  *
471  * In the case of the PNMFileType objects, since these objects are all shared,
472  * we just pull the object from the registry.
473  */
474 TypedWritable *PNMFileTypeEXR::
475 make_PNMFileTypeEXR(const FactoryParams &params) {
476  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
477 }
478 
479 #endif // HAVE_OPENEXR
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_num_channels
Returns the number of channels in the image.
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
int get_y_size() const
Returns the number of pixels in the Y direction.
int get_x_size() const
Returns the number of pixels in the X direction.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void swap_table(vector_float &table)
This is a very low-level function that completely exchanges the PfmFile's internal table of floating-...
Definition: pfmFile.I:549
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
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.
const vector_float & get_table() const
This is a very low-level function that returns a read-only reference to the internal table of floatin...
Definition: pfmFile.I:538
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
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
void clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77