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