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() {
462 BamReader::get_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
bamReader.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
BamReader::get_factory
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
FactoryParams
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PNMFileTypeRegistry::get_type_by_handle
PNMFileType * get_type_by_handle(TypeHandle handle) const
Returns the PNMFileType instance stored in the registry for the given TypeHandle, e....
Definition: pnmFileTypeRegistry.cxx:233
PNMFileTypeRegistry::get_global_ptr
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
Definition: pnmFileTypeRegistry.cxx:277
PNMFileType
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
PNMImageHeader::get_x_size
int get_x_size() const
Returns the number of pixels in the X direction.
Definition: pnmImageHeader.I:144
PNMImageHeader::get_num_channels
get_num_channels
Returns the number of channels in the image.
Definition: pnmImageHeader.h:60
PNMImageHeader::get_y_size
int get_y_size() const
Returns the number of pixels in the Y direction.
Definition: pnmImageHeader.I:153
PNMReader
This is an abstract base class that defines the interface for reading image files of various types.
Definition: pnmReader.h:27
PNMWriter
This is an abstract base class that defines the interface for writing image files of various types.
Definition: pnmWriter.h:27
PfmFile
Defines a pfm file, a 2-d table of floating-point numbers, either 3-component or 1-component,...
Definition: pfmFile.h:31
PfmFile::swap_table
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
PfmFile::get_table
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
PfmFile::clear
void clear()
Eliminates all data in the file.
Definition: pfmFile.cxx:77
TypeHandle
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
TypedWritable
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
config_pnmimagetypes.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pfmFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmFileTypeEXR.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnmFileTypeRegistry.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pixel
Definition: pnmimage_base.h:41