1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 #include <cmath>
6 #include <iomanip>
7 #include <memory>
8 
9 #include <OpenEXR/ImfTimeCode.h>  //For TimeCode support
10 
11 // Note: libdpx originally from: https://github.com/PatrickPalmer/dpx
12 // But that seems not to be actively maintained.
13 #include "libdpx/DPX.h"
14 #include "libdpx/DPXColorConverter.h"
15 
16 #include <OpenImageIO/imageio.h>
17 #include <OpenImageIO/strutil.h>
18 #include <OpenImageIO/typedesc.h>
19 
20 OIIO_PLUGIN_NAMESPACE_BEGIN
21 
22 
23 class DPXInput final : public ImageInput {
24 public:
DPXInput()25     DPXInput() { init(); }
~DPXInput()26     virtual ~DPXInput() { close(); }
format_name(void) const27     virtual const char* format_name(void) const override { return "dpx"; }
supports(string_view feature) const28     virtual int supports(string_view feature) const override
29     {
30         return (feature == "ioproxy");
31     }
32     virtual bool valid_file(const std::string& filename) const override;
33     virtual bool open(const std::string& name, ImageSpec& newspec) override;
34     virtual bool open(const std::string& name, ImageSpec& newspec,
35                       const ImageSpec& config) override;
36     virtual bool close() override;
current_subimage(void) const37     virtual int current_subimage(void) const override { return m_subimage; }
38     virtual bool seek_subimage(int subimage, int miplevel) override;
39     virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
40                                       void* data) override;
41     virtual bool read_native_scanlines(int subimage, int miplevel, int ybegin,
42                                        int yend, int z, void* data) override;
set_ioproxy(Filesystem::IOProxy * ioproxy)43     virtual bool set_ioproxy(Filesystem::IOProxy* ioproxy) override
44     {
45         m_io = ioproxy;
46         return true;
47     }
48 
49 private:
50     int m_subimage;
51     InStream* m_stream = nullptr;
52     dpx::Reader m_dpx;
53     std::vector<unsigned char> m_userBuf;
54     bool m_rawcolor;
55     std::vector<unsigned char> m_decodebuf;  // temporary decode buffer
56     std::unique_ptr<Filesystem::IOProxy> m_io_local;
57     Filesystem::IOProxy* m_io = nullptr;
58     int64_t m_io_offset       = 0;
59 
60     /// Reset everything to initial state
61     ///
init()62     void init()
63     {
64         m_subimage = -1;
65         if (m_stream) {
66             delete m_stream;
67             m_stream = nullptr;
68             m_dpx.SetInStream(nullptr);
69         }
70         m_userBuf.clear();
71         m_rawcolor = false;
72         m_io       = nullptr;
73     }
74 
75     /// Helper function - retrieve string for libdpx characteristic
76     ///
77     std::string get_characteristic_string(dpx::Characteristic c);
78 
79     /// Helper function - retrieve string for libdpx descriptor
80     ///
81     std::string get_descriptor_string(dpx::Descriptor c);
82 
83     /// Helper function - fill int array with KeyCode values
84     ///
85     void get_keycode_values(int* array);
86 
87     /// Helper function - convert Imf::TimeCode to string;
88     ///
89     std::string get_timecode_string(Imf::TimeCode& tc);
90 };
91 
92 
93 
94 // Obligatory material to make this a recognizeable imageio plugin:
95 OIIO_PLUGIN_EXPORTS_BEGIN
96 
97 OIIO_EXPORT ImageInput*
dpx_input_imageio_create()98 dpx_input_imageio_create()
99 {
100     return new DPXInput;
101 }
102 
103 OIIO_EXPORT int dpx_imageio_version = OIIO_PLUGIN_VERSION;
104 
105 OIIO_EXPORT const char*
dpx_imageio_library_version()106 dpx_imageio_library_version()
107 {
108     return nullptr;
109 }
110 
111 OIIO_EXPORT const char* dpx_input_extensions[] = { "dpx", nullptr };
112 
113 OIIO_PLUGIN_EXPORTS_END
114 
115 
116 
117 bool
valid_file(const std::string & filename) const118 DPXInput::valid_file(const std::string& filename) const
119 {
120     Filesystem::IOProxy* io
121         = new Filesystem::IOFile(filename, Filesystem::IOProxy::Mode::Read);
122     std::unique_ptr<Filesystem::IOProxy> io_uptr(io);
123     if (!io || io->mode() != Filesystem::IOProxy::Mode::Read)
124         return false;
125 
126     std::unique_ptr<InStream> stream_uptr(new InStream(io));
127     if (!stream_uptr)
128         return false;
129 
130     dpx::Reader dpx;
131     dpx.SetInStream(stream_uptr.get());
132     return dpx.ReadHeader();  // IOFile is automatically closed when destructed
133 }
134 
135 
136 
137 bool
open(const std::string & name,ImageSpec & newspec)138 DPXInput::open(const std::string& name, ImageSpec& newspec)
139 {
140     if (!m_io) {
141         // If no proxy was supplied, create a file reader
142         m_io = new Filesystem::IOFile(name, Filesystem::IOProxy::Mode::Read);
143         m_io_local.reset(m_io);
144     }
145     if (!m_io || m_io->mode() != Filesystem::IOProxy::Mode::Read) {
146         errorf("Could not open file \"%s\"", name);
147         return false;
148     }
149     m_io_offset = m_io->tell();
150 
151     m_stream = new InStream(m_io);
152     if (!m_stream) {
153         errorf("Could not open file \"%s\"", name);
154         return false;
155     }
156 
157     m_dpx.SetInStream(m_stream);
158     if (!m_dpx.ReadHeader()) {
159         errorf("Could not read header");
160         close();
161         return false;
162     }
163 
164     bool ok = seek_subimage(0, 0);
165     if (ok)
166         newspec = spec();
167     else
168         close();
169     return ok;
170 }
171 
172 
173 
174 bool
open(const std::string & name,ImageSpec & newspec,const ImageSpec & config)175 DPXInput::open(const std::string& name, ImageSpec& newspec,
176                const ImageSpec& config)
177 {
178     // Check 'config' for any special requests
179     m_rawcolor = config.get_int_attribute("dpx:RawColor")
180                  || config.get_int_attribute("dpx:RawData")  // deprecated
181                  || config.get_int_attribute("oiio:RawColor");
182     auto ioparam = config.find_attribute("oiio:ioproxy", TypeDesc::PTR);
183     if (ioparam)
184         m_io = ioparam->get<Filesystem::IOProxy*>();
185     return open(name, newspec);
186 }
187 
188 
189 
190 bool
seek_subimage(int subimage,int miplevel)191 DPXInput::seek_subimage(int subimage, int miplevel)
192 {
193     if (miplevel != 0)
194         return false;
195     if (subimage == m_subimage)
196         return true;
197     if (subimage < 0 || subimage >= m_dpx.header.ImageElementCount())
198         return false;
199 
200     m_subimage = subimage;
201 
202     // create imagespec
203     TypeDesc typedesc;
204     switch (m_dpx.header.ComponentDataSize(subimage)) {
205     case dpx::kByte:
206         typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT8
207                                                    : TypeDesc::UINT8;
208         break;
209     case dpx::kWord:
210         typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT16
211                                                    : TypeDesc::UINT16;
212         break;
213     case dpx::kInt:
214         typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT32
215                                                    : TypeDesc::UINT32;
216         break;
217     case dpx::kFloat: typedesc = TypeDesc::FLOAT; break;
218     case dpx::kDouble: typedesc = TypeDesc::DOUBLE; break;
219     default: errorf("Invalid component data size"); return false;
220     }
221     m_spec = ImageSpec(m_dpx.header.Width(), m_dpx.header.Height(),
222                        m_dpx.header.ImageElementComponentCount(subimage),
223                        typedesc);
224 
225     // xOffset/yOffset are defined as unsigned 32-bit integers, but m_spec.x/y are signed
226     // avoid casts that would result in negative values
227     if (m_dpx.header.xOffset <= (unsigned int)std::numeric_limits<int>::max())
228         m_spec.x = m_dpx.header.xOffset;
229     if (m_dpx.header.yOffset <= (unsigned int)std::numeric_limits<int>::max())
230         m_spec.y = m_dpx.header.yOffset;
231     if ((int)m_dpx.header.xOriginalSize > 0)
232         m_spec.full_width = m_dpx.header.xOriginalSize;
233     if ((int)m_dpx.header.yOriginalSize > 0)
234         m_spec.full_height = m_dpx.header.yOriginalSize;
235 
236     // fill channel names
237     m_spec.channelnames.clear();
238     switch (m_dpx.header.ImageDescriptor(subimage)) {
239     /*case dpx::kUserDefinedDescriptor:
240             break;*/
241     case dpx::kRed: m_spec.channelnames.emplace_back("R"); break;
242     case dpx::kGreen: m_spec.channelnames.emplace_back("G"); break;
243     case dpx::kBlue: m_spec.channelnames.emplace_back("B"); break;
244     case dpx::kAlpha:
245         m_spec.channelnames.emplace_back("A");
246         m_spec.alpha_channel = 0;
247         break;
248     case dpx::kLuma: m_spec.channelnames.emplace_back("Y"); break;
249     case dpx::kDepth:
250         m_spec.channelnames.emplace_back("Z");
251         m_spec.z_channel = 0;
252         break;
253     /*case dpx::kCompositeVideo:
254             break;*/
255     case dpx::kRGB:
256     case dpx::kRGBA:
257     case dpx::kABGR:  // colour converter will swap the bytes for us
258         m_spec.default_channel_names();
259         break;
260     case dpx::kCbYCrY:
261         if (m_rawcolor) {
262             m_spec.channelnames.emplace_back("CbCr");
263             m_spec.channelnames.emplace_back("Y");
264         } else {
265             m_spec.nchannels = 3;
266             m_spec.default_channel_names();
267         }
268         break;
269     case dpx::kCbYACrYA:
270         if (m_rawcolor) {
271             m_spec.channelnames.emplace_back("CbCr");
272             m_spec.channelnames.emplace_back("Y");
273             m_spec.channelnames.emplace_back("A");
274             m_spec.alpha_channel = 2;
275         } else {
276             m_spec.nchannels = 4;
277             m_spec.default_channel_names();
278         }
279         break;
280     case dpx::kCbYCr:
281         if (m_rawcolor) {
282             m_spec.channelnames.emplace_back("Cb");
283             m_spec.channelnames.emplace_back("Y");
284             m_spec.channelnames.emplace_back("Cr");
285         } else
286             m_spec.default_channel_names();
287         break;
288     case dpx::kCbYCrA:
289         if (m_rawcolor) {
290             m_spec.channelnames.emplace_back("Cb");
291             m_spec.channelnames.emplace_back("Y");
292             m_spec.channelnames.emplace_back("Cr");
293             m_spec.channelnames.emplace_back("A");
294             m_spec.alpha_channel = 3;
295         } else {
296             m_spec.default_channel_names();
297         }
298         break;
299     default: {
300         for (int i = 0; i < m_dpx.header.ImageElementComponentCount(subimage);
301              i++) {
302             std::string ch = Strutil::sprintf("channel%d", i);
303             m_spec.channelnames.push_back(ch);
304         }
305     }
306     }
307     // bits per pixel
308     m_spec.attribute("oiio:BitsPerSample", m_dpx.header.BitDepth(subimage));
309     // image orientation - see appendix B.2 of the OIIO documentation
310     int orientation;
311     switch (m_dpx.header.ImageOrientation()) {
312     case dpx::kLeftToRightTopToBottom: orientation = 1; break;
313     case dpx::kRightToLeftTopToBottom: orientation = 2; break;
314     case dpx::kLeftToRightBottomToTop: orientation = 4; break;
315     case dpx::kRightToLeftBottomToTop: orientation = 3; break;
316     case dpx::kTopToBottomLeftToRight: orientation = 5; break;
317     case dpx::kTopToBottomRightToLeft: orientation = 6; break;
318     case dpx::kBottomToTopLeftToRight: orientation = 8; break;
319     case dpx::kBottomToTopRightToLeft: orientation = 7; break;
320     default: orientation = 0; break;
321     }
322     m_spec.attribute("Orientation", orientation);
323 
324     // image linearity
325     switch (m_dpx.header.Transfer(subimage)) {
326     case dpx::kLinear: m_spec.attribute("oiio:ColorSpace", "Linear"); break;
327     case dpx::kLogarithmic:
328         m_spec.attribute("oiio:ColorSpace", "KodakLog");
329         break;
330     case dpx::kITUR709: m_spec.attribute("oiio:ColorSpace", "Rec709"); break;
331     case dpx::kUserDefined:
332         if (!std::isnan(m_dpx.header.Gamma()) && m_dpx.header.Gamma() != 0) {
333             float g = float(m_dpx.header.Gamma());
334             m_spec.attribute("oiio:ColorSpace",
335                              Strutil::sprintf("GammaCorrected%.2g", g));
336             m_spec.attribute("oiio:Gamma", g);
337             break;
338         }
339         // intentional fall-through
340     /*case dpx::kPrintingDensity:
341         case dpx::kUnspecifiedVideo:
342         case dpx::kSMPTE274M:
343         case dpx::kITUR601:
344         case dpx::kITUR602:
345         case dpx::kNTSCCompositeVideo:
346         case dpx::kPALCompositeVideo:
347         case dpx::kZLinear:
348         case dpx::kZHomogeneous:
349         case dpx::kADX:
350         case dpx::kUndefinedCharacteristic:*/
351     default: break;
352     }
353     m_spec.attribute("dpx:Transfer", get_characteristic_string(
354                                          m_dpx.header.Transfer(subimage)));
355     // colorimetric characteristic
356     m_spec.attribute("dpx:Colorimetric",
357                      get_characteristic_string(
358                          m_dpx.header.Colorimetric(subimage)));
359 
360     // general metadata
361     // some non-compliant writers will dump a field filled with 0xFF rather
362     // than a NULL string termination on the first character, so take that
363     // into account, too
364     if (m_dpx.header.copyright[0] && m_dpx.header.copyright[0] != (char)0xFF)
365         m_spec.attribute("Copyright", m_dpx.header.copyright);
366     if (m_dpx.header.creator[0] && m_dpx.header.creator[0] != (char)0xFF)
367         m_spec.attribute("Software", m_dpx.header.creator);
368     if (m_dpx.header.project[0] && m_dpx.header.project[0] != (char)0xFF)
369         m_spec.attribute("DocumentName", m_dpx.header.project);
370     if (m_dpx.header.creationTimeDate[0]) {
371         // libdpx's date/time format is pretty close to OIIO's (libdpx uses
372         // %Y:%m:%d:%H:%M:%S%Z)
373         char date[24];
374         Strutil::safe_strcpy(date, m_dpx.header.creationTimeDate, sizeof(date));
375         date[10] = ' ';
376         date[19] = 0;
377         m_spec.attribute("DateTime", date);
378     }
379     if (m_dpx.header.ImageEncoding(subimage) == dpx::kRLE)
380         m_spec.attribute("compression", "rle");
381     char buf[32 + 1];
382     m_dpx.header.Description(subimage, buf);
383     if (buf[0] && buf[0] != char(-1))
384         m_spec.attribute("ImageDescription", buf);
385     m_spec.attribute("PixelAspectRatio",
386                      m_dpx.header.AspectRatio(1)
387                          ? (m_dpx.header.AspectRatio(0)
388                             / (float)m_dpx.header.AspectRatio(1))
389                          : 1.0f);
390 
391     // DPX-specific metadata
392     m_spec.attribute("dpx:ImageDescriptor",
393                      get_descriptor_string(
394                          m_dpx.header.ImageDescriptor(subimage)));
395     // save some typing by using macros
396     // "internal" macros
397 #define DPX_SET_ATTRIB_S(x, n, s) m_spec.attribute(s, m_dpx.header.x(n))
398 #define DPX_SET_ATTRIB(x, n) DPX_SET_ATTRIB_S(x, n, "dpx:" #x)
399     // set without checking for bogus attributes
400 #define DPX_SET_ATTRIB_N(x) DPX_SET_ATTRIB(x, subimage)
401     // set with checking for bogus attributes
402 #define DPX_SET_ATTRIB_BYTE(x)    \
403     if (m_dpx.header.x() != 0xFF) \
404     DPX_SET_ATTRIB(x, )
405 #define DPX_SET_ATTRIB_INT_N(x)                 \
406     if (m_dpx.header.x(subimage) != 0xFFFFFFFF) \
407     DPX_SET_ATTRIB(x, subimage)
408 #define DPX_SET_ATTRIB_INT(x)           \
409     if (m_dpx.header.x() != 0xFFFFFFFF) \
410     DPX_SET_ATTRIB(x, )
411 #define DPX_SET_ATTRIB_FLOAT_N(x)              \
412     if (!std::isnan(m_dpx.header.x(subimage))) \
413     DPX_SET_ATTRIB(x, subimage)
414 #define DPX_SET_ATTRIB_FLOAT(x)        \
415     if (!std::isnan(m_dpx.header.x())) \
416     DPX_SET_ATTRIB(x, )
417     // see comment above Copyright, Software and DocumentName
418 #define DPX_SET_ATTRIB_STR(X, x)                            \
419     if (m_dpx.header.x[0] && m_dpx.header.x[0] != char(-1)) \
420     m_spec.attribute("dpx:" #X, m_dpx.header.x)
421 
422     DPX_SET_ATTRIB_INT(EncryptKey);
423     DPX_SET_ATTRIB_INT(DittoKey);
424     DPX_SET_ATTRIB_INT_N(LowData);
425     DPX_SET_ATTRIB_FLOAT_N(LowQuantity);
426     DPX_SET_ATTRIB_INT_N(HighData);
427     DPX_SET_ATTRIB_FLOAT_N(HighQuantity);
428     DPX_SET_ATTRIB_INT_N(EndOfLinePadding);
429     DPX_SET_ATTRIB_INT_N(EndOfImagePadding);
430     DPX_SET_ATTRIB_FLOAT(XScannedSize);
431     DPX_SET_ATTRIB_FLOAT(YScannedSize);
432     DPX_SET_ATTRIB_INT(FramePosition);
433     DPX_SET_ATTRIB_INT(SequenceLength);
434     DPX_SET_ATTRIB_INT(HeldCount);
435     DPX_SET_ATTRIB_FLOAT(FrameRate);
436     DPX_SET_ATTRIB_FLOAT(ShutterAngle);
437     DPX_SET_ATTRIB_STR(Version, version);
438     DPX_SET_ATTRIB_STR(Format, format);
439     DPX_SET_ATTRIB_STR(FrameId, frameId);
440     DPX_SET_ATTRIB_STR(SlateInfo, slateInfo);
441     DPX_SET_ATTRIB_STR(SourceImageFileName, sourceImageFileName);
442     DPX_SET_ATTRIB_STR(InputDevice, inputDevice);
443     DPX_SET_ATTRIB_STR(InputDeviceSerialNumber, inputDeviceSerialNumber);
444     DPX_SET_ATTRIB_BYTE(Interlace);
445     DPX_SET_ATTRIB_BYTE(FieldNumber);
446     DPX_SET_ATTRIB_FLOAT(HorizontalSampleRate);
447     DPX_SET_ATTRIB_FLOAT(VerticalSampleRate);
448     DPX_SET_ATTRIB_FLOAT(TemporalFrameRate);
449     DPX_SET_ATTRIB_FLOAT(TimeOffset);
450     DPX_SET_ATTRIB_FLOAT(BlackLevel);
451     DPX_SET_ATTRIB_FLOAT(BlackGain);
452     DPX_SET_ATTRIB_FLOAT(BreakPoint);
453     DPX_SET_ATTRIB_FLOAT(WhiteLevel);
454     DPX_SET_ATTRIB_FLOAT(IntegrationTimes);
455 
456 #undef DPX_SET_ATTRIB_STR
457 #undef DPX_SET_ATTRIB_FLOAT
458 #undef DPX_SET_ATTRIB_FLOAT_N
459 #undef DPX_SET_ATTRIB_INT
460 #undef DPX_SET_ATTRIB_INT_N
461 #undef DPX_SET_ATTRIB_N
462 #undef DPX_SET_ATTRIB
463 #undef DPX_SET_ATTRIB_S
464 
465     std::string tmpstr;
466     switch (m_dpx.header.ImagePacking(subimage)) {
467     case dpx::kPacked: tmpstr = "Packed"; break;
468     case dpx::kFilledMethodA: tmpstr = "Filled, method A"; break;
469     case dpx::kFilledMethodB: tmpstr = "Filled, method B"; break;
470     }
471     if (!tmpstr.empty())
472         m_spec.attribute("dpx:Packing", tmpstr);
473 
474     if (m_dpx.header.filmManufacturingIdCode[0] != 0) {
475         int kc[7];
476         get_keycode_values(kc);
477         m_spec.attribute("smpte:KeyCode", TypeKeyCode, kc);
478     }
479 
480     if (m_dpx.header.timeCode != 0xFFFFFFFF) {
481         unsigned int timecode[2] = { m_dpx.header.timeCode,
482                                      m_dpx.header.userBits };
483         m_spec.attribute("smpte:TimeCode", TypeTimeCode, timecode);
484 
485         // This attribute is dpx specific and is left in for backwards compatability.
486         // Users should utilise the new smpte:TimeCode attribute instead
487         Imf::TimeCode tc(m_dpx.header.timeCode, m_dpx.header.userBits);
488         m_spec.attribute("dpx:TimeCode", get_timecode_string(tc));
489     }
490 
491     // This attribute is dpx specific and is left in for backwards compatability.
492     // Users should utilise the new smpte:TimeCode attribute instead
493     if (m_dpx.header.userBits != 0xFFFFFFFF)
494         m_spec.attribute("dpx:UserBits", m_dpx.header.userBits);
495 
496     if (m_dpx.header.sourceTimeDate[0]) {
497         // libdpx's date/time format is pretty close to OIIO's (libdpx uses
498         // %Y:%m:%d:%H:%M:%S%Z)
499         char date[24];
500         Strutil::safe_strcpy(date, m_dpx.header.sourceTimeDate, sizeof(date));
501         date[10] = ' ';
502         date[19] = 0;
503         m_spec.attribute("dpx:SourceDateTime", date);
504     }
505     m_dpx.header.FilmEdgeCode(buf);
506     if (buf[0])
507         m_spec.attribute("dpx:FilmEdgeCode", buf);
508 
509     tmpstr.clear();
510     switch (m_dpx.header.Signal()) {
511     case dpx::kUndefined: tmpstr = "Undefined"; break;
512     case dpx::kNTSC: tmpstr = "NTSC"; break;
513     case dpx::kPAL: tmpstr = "PAL"; break;
514     case dpx::kPAL_M: tmpstr = "PAL-M"; break;
515     case dpx::kSECAM: tmpstr = "SECAM"; break;
516     case dpx::k525LineInterlace43AR:
517         tmpstr = "YCbCr ITU-R 601-5 525i, 4:3";
518         break;
519     case dpx::k625LineInterlace43AR:
520         tmpstr = "YCbCr ITU-R 601-5 625i, 4:3";
521         break;
522     case dpx::k525LineInterlace169AR:
523         tmpstr = "YCbCr ITU-R 601-5 525i, 16:9";
524         break;
525     case dpx::k625LineInterlace169AR:
526         tmpstr = "YCbCr ITU-R 601-5 625i, 16:9";
527         break;
528     case dpx::k1050LineInterlace169AR: tmpstr = "YCbCr 1050i, 16:9"; break;
529     case dpx::k1125LineInterlace169AR_274:
530         tmpstr = "YCbCr 1125i, 16:9 (SMPTE 274M)";
531         break;
532     case dpx::k1250LineInterlace169AR: tmpstr = "YCbCr 1250i, 16:9"; break;
533     case dpx::k1125LineInterlace169AR_240:
534         tmpstr = "YCbCr 1125i, 16:9 (SMPTE 240M)";
535         break;
536     case dpx::k525LineProgressive169AR: tmpstr = "YCbCr 525p, 16:9"; break;
537     case dpx::k625LineProgressive169AR: tmpstr = "YCbCr 625p, 16:9"; break;
538     case dpx::k750LineProgressive169AR:
539         tmpstr = "YCbCr 750p, 16:9 (SMPTE 296M)";
540         break;
541     case dpx::k1125LineProgressive169AR:
542         tmpstr = "YCbCr 1125p, 16:9 (SMPTE 274M)";
543         break;
544     case dpx::k255:
545         // don't set the attribute at all
546         break;
547     default:
548         tmpstr = Strutil::sprintf("Undefined %d", (int)m_dpx.header.Signal());
549         break;
550     }
551     if (!tmpstr.empty())
552         m_spec.attribute("dpx:Signal", tmpstr);
553 
554     // read in user data; don't bother if the buffer is already filled (user
555     // data is per-file, not per-element)
556     if (m_userBuf.empty() && m_dpx.header.UserSize() != 0
557         && m_dpx.header.UserSize() != 0xFFFFFFFF) {
558         m_userBuf.resize(m_dpx.header.UserSize());
559         m_dpx.ReadUserData(&m_userBuf[0]);
560     }
561     if (!m_userBuf.empty())
562         m_spec.attribute("dpx:UserData",
563                          TypeDesc(TypeDesc::UCHAR, m_dpx.header.UserSize()),
564                          &m_userBuf[0]);
565 
566     // All of the 1-channel encoding options also behave like "rawcolor",
567     // not needing color space transformations.
568     if (m_spec.nchannels == 1)
569         m_rawcolor = true;
570 
571     return true;
572 }
573 
574 
575 
576 bool
close()577 DPXInput::close()
578 {
579     if (m_io_local) {
580         // If we allocated our own ioproxy, close it.
581         m_io_local.reset();
582         m_io = nullptr;
583     }
584     // N.B. If we were passed an ioproxy from the user (m_io != nullptr, but
585     // m_io_local was not set), don't actually close it, it belongs to the
586     // caller. And in the case of an ImageCache file, it's possible that the
587     // IC won't close this ImageInput until after the owner of the IOProxy
588     // destroyed it. So don't mess with it here in close() at all, because
589     // we just can't be sure if it's still alive or not.
590 
591     init();  // Reset to initial state
592     return true;
593 }
594 
595 
596 
597 bool
read_native_scanline(int subimage,int miplevel,int y,int z,void * data)598 DPXInput::read_native_scanline(int subimage, int miplevel, int y, int z,
599                                void* data)
600 {
601     return read_native_scanlines(subimage, miplevel, y, y + 1, z, data);
602 }
603 
604 
605 
606 bool
read_native_scanlines(int subimage,int miplevel,int ybegin,int yend,int,void * data)607 DPXInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
608                                 int yend, int /*z*/, void* data)
609 {
610     lock_guard lock(m_mutex);
611     if (!seek_subimage(subimage, miplevel))
612         return false;
613 
614     dpx::Block block(0, ybegin - m_spec.y, m_dpx.header.Width() - 1,
615                      yend - 1 - m_spec.y);
616 
617     if (m_rawcolor) {
618         // fast path - just read the scanline in
619         if (!m_dpx.ReadBlock(subimage, (unsigned char*)data, block))
620             return false;
621     } else {
622         // read the scanline and convert to RGB
623         unsigned char* ptr = (unsigned char*)data;
624         int bufsize = dpx::QueryRGBBufferSize(m_dpx.header, subimage, block);
625         if (bufsize > 0) {
626             m_decodebuf.resize(bufsize);
627             ptr = m_decodebuf.data();
628         }
629 
630         if (!m_dpx.ReadBlock(subimage, ptr, block))
631             return false;
632         if (!dpx::ConvertToRGB(m_dpx.header, subimage, ptr, data, block))
633             return false;
634     }
635 
636     return true;
637 }
638 
639 
640 
641 std::string
get_characteristic_string(dpx::Characteristic c)642 DPXInput::get_characteristic_string(dpx::Characteristic c)
643 {
644     switch (c) {
645     case dpx::kUserDefined: return "User defined";
646     case dpx::kPrintingDensity: return "Printing density";
647     case dpx::kLinear: return "Linear";
648     case dpx::kLogarithmic: return "Logarithmic";
649     case dpx::kUnspecifiedVideo: return "Unspecified video";
650     case dpx::kSMPTE274M: return "SMPTE 274M";
651     case dpx::kITUR709: return "ITU-R 709-4";
652     case dpx::kITUR601: return "ITU-R 601-5 system B or G";
653     case dpx::kITUR602: return "ITU-R 601-5 system M";
654     case dpx::kNTSCCompositeVideo: return "NTSC composite video";
655     case dpx::kPALCompositeVideo: return "PAL composite video";
656     case dpx::kZLinear: return "Z depth linear";
657     case dpx::kZHomogeneous: return "Z depth homogeneous";
658     case dpx::kADX: return "ADX";
659     case dpx::kUndefinedCharacteristic:
660     default: return "Undefined";
661     }
662 }
663 
664 
665 
666 std::string
get_descriptor_string(dpx::Descriptor c)667 DPXInput::get_descriptor_string(dpx::Descriptor c)
668 {
669     switch (c) {
670     case dpx::kUserDefinedDescriptor:
671     case dpx::kUserDefined2Comp:
672     case dpx::kUserDefined3Comp:
673     case dpx::kUserDefined4Comp:
674     case dpx::kUserDefined5Comp:
675     case dpx::kUserDefined6Comp:
676     case dpx::kUserDefined7Comp:
677     case dpx::kUserDefined8Comp: return "User defined";
678     case dpx::kRed: return "Red";
679     case dpx::kGreen: return "Green";
680     case dpx::kBlue: return "Blue";
681     case dpx::kAlpha: return "Alpha";
682     case dpx::kLuma: return "Luma";
683     case dpx::kColorDifference: return "Color difference";
684     case dpx::kDepth: return "Depth";
685     case dpx::kCompositeVideo: return "Composite video";
686     case dpx::kRGB: return "RGB";
687     case dpx::kRGBA: return "RGBA";
688     case dpx::kABGR: return "ABGR";
689     case dpx::kCbYCrY: return "CbYCrY";
690     case dpx::kCbYACrYA: return "CbYACrYA";
691     case dpx::kCbYCr: return "CbYCr";
692     case dpx::kCbYCrA: return "CbYCrA";
693     //case dpx::kUndefinedDescriptor:
694     default: return "Undefined";
695     }
696 }
697 
698 
699 
700 void
get_keycode_values(int * array)701 DPXInput::get_keycode_values(int* array)
702 {
703     std::stringstream ss;
704 
705     // Manufacturer code
706     ss << std::string(m_dpx.header.filmManufacturingIdCode, 2);
707     ss >> array[0];
708     ss.clear();
709     ss.str("");
710 
711     // Film type
712     ss << std::string(m_dpx.header.filmType, 2);
713     ss >> array[1];
714     ss.clear();
715     ss.str("");
716 
717     // Prefix
718     ss << std::string(m_dpx.header.prefix, 6);
719     ss >> array[2];
720     ss.clear();
721     ss.str("");
722 
723     // Count
724     ss << std::string(m_dpx.header.count, 4);
725     ss >> array[3];
726     ss.clear();
727     ss.str("");
728 
729     // Perforation Offset
730     ss << std::string(m_dpx.header.perfsOffset, 2);
731     ss >> array[4];
732     ss.clear();
733     ss.str("");
734 
735     // Format
736     std::string format(m_dpx.header.format, 32);
737     int& perfsPerFrame = array[5];
738     int& perfsPerCount = array[6];
739 
740     // default values
741     perfsPerFrame = 4;
742     perfsPerCount = 64;
743 
744     if (format == "8kimax") {
745         perfsPerFrame = 15;
746         perfsPerCount = 120;
747     } else if (format.substr(0, 4) == "2kvv" || format.substr(0, 4) == "4kvv") {
748         perfsPerFrame = 8;
749     } else if (format == "VistaVision") {
750         perfsPerFrame = 8;
751     } else if (format.substr(0, 4) == "2k35" || format.substr(0, 4) == "4k35") {
752         perfsPerFrame = 4;
753     } else if (format == "Full Aperture") {
754         perfsPerFrame = 4;
755     } else if (format == "Academy") {
756         perfsPerFrame = 4;
757     } else if (format.substr(0, 7) == "2k3perf"
758                || format.substr(0, 7) == "4k3perf") {
759         perfsPerFrame = 3;
760     } else if (format == "3perf") {
761         perfsPerFrame = 3;
762     }
763 }
764 
765 
766 
767 std::string
get_timecode_string(Imf::TimeCode & tc)768 DPXInput::get_timecode_string(Imf::TimeCode& tc)
769 {
770     int values[] = { tc.hours(), tc.minutes(), tc.seconds(), tc.frame() };
771     std::stringstream ss;
772     for (int i = 0; i < 4; i++) {
773         std::ostringstream padded;
774         padded << std::setw(2) << std::setfill('0') << values[i];
775         ss << padded.str();
776         if (i != 3) {
777             if (i == 2) {
778                 tc.dropFrame() ? ss << ';' : ss << ':';
779             } else {
780                 ss << ':';
781             }
782         }
783     }
784     return ss.str();
785 }
786 
787 OIIO_PLUGIN_NAMESPACE_END
788