// Copyright 2008-present Contributors to the OpenImageIO project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md

#include <cmath>
#include <iomanip>
#include <memory>

#include <OpenEXR/ImfTimeCode.h>  //For TimeCode support

// Note: libdpx originally from: https://github.com/PatrickPalmer/dpx
// But that seems not to be actively maintained.
#include "libdpx/DPX.h"
#include "libdpx/DPXColorConverter.h"

#include <OpenImageIO/imageio.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/typedesc.h>

OIIO_PLUGIN_NAMESPACE_BEGIN


class DPXInput final : public ImageInput {
public:
    DPXInput() { init(); }
    virtual ~DPXInput() { close(); }
    virtual const char* format_name(void) const override { return "dpx"; }
    virtual int supports(string_view feature) const override
    {
        return (feature == "ioproxy");
    }
    virtual bool valid_file(const std::string& filename) const override;
    virtual bool open(const std::string& name, ImageSpec& newspec) override;
    virtual bool open(const std::string& name, ImageSpec& newspec,
                      const ImageSpec& config) override;
    virtual bool close() override;
    virtual int current_subimage(void) const override { return m_subimage; }
    virtual bool seek_subimage(int subimage, int miplevel) override;
    virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
                                      void* data) override;
    virtual bool read_native_scanlines(int subimage, int miplevel, int ybegin,
                                       int yend, int z, void* data) override;
    virtual bool set_ioproxy(Filesystem::IOProxy* ioproxy) override
    {
        m_io = ioproxy;
        return true;
    }

private:
    int m_subimage;
    InStream* m_stream = nullptr;
    dpx::Reader m_dpx;
    std::vector<unsigned char> m_userBuf;
    bool m_rawcolor;
    std::vector<unsigned char> m_decodebuf;  // temporary decode buffer
    std::unique_ptr<Filesystem::IOProxy> m_io_local;
    Filesystem::IOProxy* m_io = nullptr;
    int64_t m_io_offset       = 0;

    /// Reset everything to initial state
    ///
    void init()
    {
        m_subimage = -1;
        if (m_stream) {
            delete m_stream;
            m_stream = nullptr;
            m_dpx.SetInStream(nullptr);
        }
        m_userBuf.clear();
        m_rawcolor = false;
        m_io       = nullptr;
    }

    /// Helper function - retrieve string for libdpx characteristic
    ///
    std::string get_characteristic_string(dpx::Characteristic c);

    /// Helper function - retrieve string for libdpx descriptor
    ///
    std::string get_descriptor_string(dpx::Descriptor c);

    /// Helper function - fill int array with KeyCode values
    ///
    void get_keycode_values(int* array);

    /// Helper function - convert Imf::TimeCode to string;
    ///
    std::string get_timecode_string(Imf::TimeCode& tc);
};



// Obligatory material to make this a recognizeable imageio plugin:
OIIO_PLUGIN_EXPORTS_BEGIN

OIIO_EXPORT ImageInput*
dpx_input_imageio_create()
{
    return new DPXInput;
}

OIIO_EXPORT int dpx_imageio_version = OIIO_PLUGIN_VERSION;

OIIO_EXPORT const char*
dpx_imageio_library_version()
{
    return nullptr;
}

OIIO_EXPORT const char* dpx_input_extensions[] = { "dpx", nullptr };

OIIO_PLUGIN_EXPORTS_END



bool
DPXInput::valid_file(const std::string& filename) const
{
    Filesystem::IOProxy* io
        = new Filesystem::IOFile(filename, Filesystem::IOProxy::Mode::Read);
    std::unique_ptr<Filesystem::IOProxy> io_uptr(io);
    if (!io || io->mode() != Filesystem::IOProxy::Mode::Read)
        return false;

    std::unique_ptr<InStream> stream_uptr(new InStream(io));
    if (!stream_uptr)
        return false;

    dpx::Reader dpx;
    dpx.SetInStream(stream_uptr.get());
    return dpx.ReadHeader();  // IOFile is automatically closed when destructed
}



bool
DPXInput::open(const std::string& name, ImageSpec& newspec)
{
    if (!m_io) {
        // If no proxy was supplied, create a file reader
        m_io = new Filesystem::IOFile(name, Filesystem::IOProxy::Mode::Read);
        m_io_local.reset(m_io);
    }
    if (!m_io || m_io->mode() != Filesystem::IOProxy::Mode::Read) {
        errorf("Could not open file \"%s\"", name);
        return false;
    }
    m_io_offset = m_io->tell();

    m_stream = new InStream(m_io);
    if (!m_stream) {
        errorf("Could not open file \"%s\"", name);
        return false;
    }

    m_dpx.SetInStream(m_stream);
    if (!m_dpx.ReadHeader()) {
        errorf("Could not read header");
        close();
        return false;
    }

    bool ok = seek_subimage(0, 0);
    if (ok)
        newspec = spec();
    else
        close();
    return ok;
}



bool
DPXInput::open(const std::string& name, ImageSpec& newspec,
               const ImageSpec& config)
{
    // Check 'config' for any special requests
    m_rawcolor = config.get_int_attribute("dpx:RawColor")
                 || config.get_int_attribute("dpx:RawData")  // deprecated
                 || config.get_int_attribute("oiio:RawColor");
    auto ioparam = config.find_attribute("oiio:ioproxy", TypeDesc::PTR);
    if (ioparam)
        m_io = ioparam->get<Filesystem::IOProxy*>();
    return open(name, newspec);
}



bool
DPXInput::seek_subimage(int subimage, int miplevel)
{
    if (miplevel != 0)
        return false;
    if (subimage == m_subimage)
        return true;
    if (subimage < 0 || subimage >= m_dpx.header.ImageElementCount())
        return false;

    m_subimage = subimage;

    // create imagespec
    TypeDesc typedesc;
    switch (m_dpx.header.ComponentDataSize(subimage)) {
    case dpx::kByte:
        typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT8
                                                   : TypeDesc::UINT8;
        break;
    case dpx::kWord:
        typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT16
                                                   : TypeDesc::UINT16;
        break;
    case dpx::kInt:
        typedesc = m_dpx.header.DataSign(subimage) ? TypeDesc::INT32
                                                   : TypeDesc::UINT32;
        break;
    case dpx::kFloat: typedesc = TypeDesc::FLOAT; break;
    case dpx::kDouble: typedesc = TypeDesc::DOUBLE; break;
    default: errorf("Invalid component data size"); return false;
    }
    m_spec = ImageSpec(m_dpx.header.Width(), m_dpx.header.Height(),
                       m_dpx.header.ImageElementComponentCount(subimage),
                       typedesc);

    // xOffset/yOffset are defined as unsigned 32-bit integers, but m_spec.x/y are signed
    // avoid casts that would result in negative values
    if (m_dpx.header.xOffset <= (unsigned int)std::numeric_limits<int>::max())
        m_spec.x = m_dpx.header.xOffset;
    if (m_dpx.header.yOffset <= (unsigned int)std::numeric_limits<int>::max())
        m_spec.y = m_dpx.header.yOffset;
    if ((int)m_dpx.header.xOriginalSize > 0)
        m_spec.full_width = m_dpx.header.xOriginalSize;
    if ((int)m_dpx.header.yOriginalSize > 0)
        m_spec.full_height = m_dpx.header.yOriginalSize;

    // fill channel names
    m_spec.channelnames.clear();
    switch (m_dpx.header.ImageDescriptor(subimage)) {
    /*case dpx::kUserDefinedDescriptor:
            break;*/
    case dpx::kRed: m_spec.channelnames.emplace_back("R"); break;
    case dpx::kGreen: m_spec.channelnames.emplace_back("G"); break;
    case dpx::kBlue: m_spec.channelnames.emplace_back("B"); break;
    case dpx::kAlpha:
        m_spec.channelnames.emplace_back("A");
        m_spec.alpha_channel = 0;
        break;
    case dpx::kLuma: m_spec.channelnames.emplace_back("Y"); break;
    case dpx::kDepth:
        m_spec.channelnames.emplace_back("Z");
        m_spec.z_channel = 0;
        break;
    /*case dpx::kCompositeVideo:
            break;*/
    case dpx::kRGB:
    case dpx::kRGBA:
    case dpx::kABGR:  // colour converter will swap the bytes for us
        m_spec.default_channel_names();
        break;
    case dpx::kCbYCrY:
        if (m_rawcolor) {
            m_spec.channelnames.emplace_back("CbCr");
            m_spec.channelnames.emplace_back("Y");
        } else {
            m_spec.nchannels = 3;
            m_spec.default_channel_names();
        }
        break;
    case dpx::kCbYACrYA:
        if (m_rawcolor) {
            m_spec.channelnames.emplace_back("CbCr");
            m_spec.channelnames.emplace_back("Y");
            m_spec.channelnames.emplace_back("A");
            m_spec.alpha_channel = 2;
        } else {
            m_spec.nchannels = 4;
            m_spec.default_channel_names();
        }
        break;
    case dpx::kCbYCr:
        if (m_rawcolor) {
            m_spec.channelnames.emplace_back("Cb");
            m_spec.channelnames.emplace_back("Y");
            m_spec.channelnames.emplace_back("Cr");
        } else
            m_spec.default_channel_names();
        break;
    case dpx::kCbYCrA:
        if (m_rawcolor) {
            m_spec.channelnames.emplace_back("Cb");
            m_spec.channelnames.emplace_back("Y");
            m_spec.channelnames.emplace_back("Cr");
            m_spec.channelnames.emplace_back("A");
            m_spec.alpha_channel = 3;
        } else {
            m_spec.default_channel_names();
        }
        break;
    default: {
        for (int i = 0; i < m_dpx.header.ImageElementComponentCount(subimage);
             i++) {
            std::string ch = Strutil::sprintf("channel%d", i);
            m_spec.channelnames.push_back(ch);
        }
    }
    }
    // bits per pixel
    m_spec.attribute("oiio:BitsPerSample", m_dpx.header.BitDepth(subimage));
    // image orientation - see appendix B.2 of the OIIO documentation
    int orientation;
    switch (m_dpx.header.ImageOrientation()) {
    case dpx::kLeftToRightTopToBottom: orientation = 1; break;
    case dpx::kRightToLeftTopToBottom: orientation = 2; break;
    case dpx::kLeftToRightBottomToTop: orientation = 4; break;
    case dpx::kRightToLeftBottomToTop: orientation = 3; break;
    case dpx::kTopToBottomLeftToRight: orientation = 5; break;
    case dpx::kTopToBottomRightToLeft: orientation = 6; break;
    case dpx::kBottomToTopLeftToRight: orientation = 8; break;
    case dpx::kBottomToTopRightToLeft: orientation = 7; break;
    default: orientation = 0; break;
    }
    m_spec.attribute("Orientation", orientation);

    // image linearity
    switch (m_dpx.header.Transfer(subimage)) {
    case dpx::kLinear: m_spec.attribute("oiio:ColorSpace", "Linear"); break;
    case dpx::kLogarithmic:
        m_spec.attribute("oiio:ColorSpace", "KodakLog");
        break;
    case dpx::kITUR709: m_spec.attribute("oiio:ColorSpace", "Rec709"); break;
    case dpx::kUserDefined:
        if (!std::isnan(m_dpx.header.Gamma()) && m_dpx.header.Gamma() != 0) {
            float g = float(m_dpx.header.Gamma());
            m_spec.attribute("oiio:ColorSpace",
                             Strutil::sprintf("GammaCorrected%.2g", g));
            m_spec.attribute("oiio:Gamma", g);
            break;
        }
        // intentional fall-through
    /*case dpx::kPrintingDensity:
        case dpx::kUnspecifiedVideo:
        case dpx::kSMPTE274M:
        case dpx::kITUR601:
        case dpx::kITUR602:
        case dpx::kNTSCCompositeVideo:
        case dpx::kPALCompositeVideo:
        case dpx::kZLinear:
        case dpx::kZHomogeneous:
        case dpx::kADX:
        case dpx::kUndefinedCharacteristic:*/
    default: break;
    }
    m_spec.attribute("dpx:Transfer", get_characteristic_string(
                                         m_dpx.header.Transfer(subimage)));
    // colorimetric characteristic
    m_spec.attribute("dpx:Colorimetric",
                     get_characteristic_string(
                         m_dpx.header.Colorimetric(subimage)));

    // general metadata
    // some non-compliant writers will dump a field filled with 0xFF rather
    // than a NULL string termination on the first character, so take that
    // into account, too
    if (m_dpx.header.copyright[0] && m_dpx.header.copyright[0] != (char)0xFF)
        m_spec.attribute("Copyright", m_dpx.header.copyright);
    if (m_dpx.header.creator[0] && m_dpx.header.creator[0] != (char)0xFF)
        m_spec.attribute("Software", m_dpx.header.creator);
    if (m_dpx.header.project[0] && m_dpx.header.project[0] != (char)0xFF)
        m_spec.attribute("DocumentName", m_dpx.header.project);
    if (m_dpx.header.creationTimeDate[0]) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        char date[24];
        Strutil::safe_strcpy(date, m_dpx.header.creationTimeDate, sizeof(date));
        date[10] = ' ';
        date[19] = 0;
        m_spec.attribute("DateTime", date);
    }
    if (m_dpx.header.ImageEncoding(subimage) == dpx::kRLE)
        m_spec.attribute("compression", "rle");
    char buf[32 + 1];
    m_dpx.header.Description(subimage, buf);
    if (buf[0] && buf[0] != char(-1))
        m_spec.attribute("ImageDescription", buf);
    m_spec.attribute("PixelAspectRatio",
                     m_dpx.header.AspectRatio(1)
                         ? (m_dpx.header.AspectRatio(0)
                            / (float)m_dpx.header.AspectRatio(1))
                         : 1.0f);

    // DPX-specific metadata
    m_spec.attribute("dpx:ImageDescriptor",
                     get_descriptor_string(
                         m_dpx.header.ImageDescriptor(subimage)));
    // save some typing by using macros
    // "internal" macros
#define DPX_SET_ATTRIB_S(x, n, s) m_spec.attribute(s, m_dpx.header.x(n))
#define DPX_SET_ATTRIB(x, n) DPX_SET_ATTRIB_S(x, n, "dpx:" #x)
    // set without checking for bogus attributes
#define DPX_SET_ATTRIB_N(x) DPX_SET_ATTRIB(x, subimage)
    // set with checking for bogus attributes
#define DPX_SET_ATTRIB_BYTE(x)    \
    if (m_dpx.header.x() != 0xFF) \
    DPX_SET_ATTRIB(x, )
#define DPX_SET_ATTRIB_INT_N(x)                 \
    if (m_dpx.header.x(subimage) != 0xFFFFFFFF) \
    DPX_SET_ATTRIB(x, subimage)
#define DPX_SET_ATTRIB_INT(x)           \
    if (m_dpx.header.x() != 0xFFFFFFFF) \
    DPX_SET_ATTRIB(x, )
#define DPX_SET_ATTRIB_FLOAT_N(x)              \
    if (!std::isnan(m_dpx.header.x(subimage))) \
    DPX_SET_ATTRIB(x, subimage)
#define DPX_SET_ATTRIB_FLOAT(x)        \
    if (!std::isnan(m_dpx.header.x())) \
    DPX_SET_ATTRIB(x, )
    // see comment above Copyright, Software and DocumentName
#define DPX_SET_ATTRIB_STR(X, x)                            \
    if (m_dpx.header.x[0] && m_dpx.header.x[0] != char(-1)) \
    m_spec.attribute("dpx:" #X, m_dpx.header.x)

    DPX_SET_ATTRIB_INT(EncryptKey);
    DPX_SET_ATTRIB_INT(DittoKey);
    DPX_SET_ATTRIB_INT_N(LowData);
    DPX_SET_ATTRIB_FLOAT_N(LowQuantity);
    DPX_SET_ATTRIB_INT_N(HighData);
    DPX_SET_ATTRIB_FLOAT_N(HighQuantity);
    DPX_SET_ATTRIB_INT_N(EndOfLinePadding);
    DPX_SET_ATTRIB_INT_N(EndOfImagePadding);
    DPX_SET_ATTRIB_FLOAT(XScannedSize);
    DPX_SET_ATTRIB_FLOAT(YScannedSize);
    DPX_SET_ATTRIB_INT(FramePosition);
    DPX_SET_ATTRIB_INT(SequenceLength);
    DPX_SET_ATTRIB_INT(HeldCount);
    DPX_SET_ATTRIB_FLOAT(FrameRate);
    DPX_SET_ATTRIB_FLOAT(ShutterAngle);
    DPX_SET_ATTRIB_STR(Version, version);
    DPX_SET_ATTRIB_STR(Format, format);
    DPX_SET_ATTRIB_STR(FrameId, frameId);
    DPX_SET_ATTRIB_STR(SlateInfo, slateInfo);
    DPX_SET_ATTRIB_STR(SourceImageFileName, sourceImageFileName);
    DPX_SET_ATTRIB_STR(InputDevice, inputDevice);
    DPX_SET_ATTRIB_STR(InputDeviceSerialNumber, inputDeviceSerialNumber);
    DPX_SET_ATTRIB_BYTE(Interlace);
    DPX_SET_ATTRIB_BYTE(FieldNumber);
    DPX_SET_ATTRIB_FLOAT(HorizontalSampleRate);
    DPX_SET_ATTRIB_FLOAT(VerticalSampleRate);
    DPX_SET_ATTRIB_FLOAT(TemporalFrameRate);
    DPX_SET_ATTRIB_FLOAT(TimeOffset);
    DPX_SET_ATTRIB_FLOAT(BlackLevel);
    DPX_SET_ATTRIB_FLOAT(BlackGain);
    DPX_SET_ATTRIB_FLOAT(BreakPoint);
    DPX_SET_ATTRIB_FLOAT(WhiteLevel);
    DPX_SET_ATTRIB_FLOAT(IntegrationTimes);

#undef DPX_SET_ATTRIB_STR
#undef DPX_SET_ATTRIB_FLOAT
#undef DPX_SET_ATTRIB_FLOAT_N
#undef DPX_SET_ATTRIB_INT
#undef DPX_SET_ATTRIB_INT_N
#undef DPX_SET_ATTRIB_N
#undef DPX_SET_ATTRIB
#undef DPX_SET_ATTRIB_S

    std::string tmpstr;
    switch (m_dpx.header.ImagePacking(subimage)) {
    case dpx::kPacked: tmpstr = "Packed"; break;
    case dpx::kFilledMethodA: tmpstr = "Filled, method A"; break;
    case dpx::kFilledMethodB: tmpstr = "Filled, method B"; break;
    }
    if (!tmpstr.empty())
        m_spec.attribute("dpx:Packing", tmpstr);

    if (m_dpx.header.filmManufacturingIdCode[0] != 0) {
        int kc[7];
        get_keycode_values(kc);
        m_spec.attribute("smpte:KeyCode", TypeKeyCode, kc);
    }

    if (m_dpx.header.timeCode != 0xFFFFFFFF) {
        unsigned int timecode[2] = { m_dpx.header.timeCode,
                                     m_dpx.header.userBits };
        m_spec.attribute("smpte:TimeCode", TypeTimeCode, timecode);

        // This attribute is dpx specific and is left in for backwards compatability.
        // Users should utilise the new smpte:TimeCode attribute instead
        Imf::TimeCode tc(m_dpx.header.timeCode, m_dpx.header.userBits);
        m_spec.attribute("dpx:TimeCode", get_timecode_string(tc));
    }

    // This attribute is dpx specific and is left in for backwards compatability.
    // Users should utilise the new smpte:TimeCode attribute instead
    if (m_dpx.header.userBits != 0xFFFFFFFF)
        m_spec.attribute("dpx:UserBits", m_dpx.header.userBits);

    if (m_dpx.header.sourceTimeDate[0]) {
        // libdpx's date/time format is pretty close to OIIO's (libdpx uses
        // %Y:%m:%d:%H:%M:%S%Z)
        char date[24];
        Strutil::safe_strcpy(date, m_dpx.header.sourceTimeDate, sizeof(date));
        date[10] = ' ';
        date[19] = 0;
        m_spec.attribute("dpx:SourceDateTime", date);
    }
    m_dpx.header.FilmEdgeCode(buf);
    if (buf[0])
        m_spec.attribute("dpx:FilmEdgeCode", buf);

    tmpstr.clear();
    switch (m_dpx.header.Signal()) {
    case dpx::kUndefined: tmpstr = "Undefined"; break;
    case dpx::kNTSC: tmpstr = "NTSC"; break;
    case dpx::kPAL: tmpstr = "PAL"; break;
    case dpx::kPAL_M: tmpstr = "PAL-M"; break;
    case dpx::kSECAM: tmpstr = "SECAM"; break;
    case dpx::k525LineInterlace43AR:
        tmpstr = "YCbCr ITU-R 601-5 525i, 4:3";
        break;
    case dpx::k625LineInterlace43AR:
        tmpstr = "YCbCr ITU-R 601-5 625i, 4:3";
        break;
    case dpx::k525LineInterlace169AR:
        tmpstr = "YCbCr ITU-R 601-5 525i, 16:9";
        break;
    case dpx::k625LineInterlace169AR:
        tmpstr = "YCbCr ITU-R 601-5 625i, 16:9";
        break;
    case dpx::k1050LineInterlace169AR: tmpstr = "YCbCr 1050i, 16:9"; break;
    case dpx::k1125LineInterlace169AR_274:
        tmpstr = "YCbCr 1125i, 16:9 (SMPTE 274M)";
        break;
    case dpx::k1250LineInterlace169AR: tmpstr = "YCbCr 1250i, 16:9"; break;
    case dpx::k1125LineInterlace169AR_240:
        tmpstr = "YCbCr 1125i, 16:9 (SMPTE 240M)";
        break;
    case dpx::k525LineProgressive169AR: tmpstr = "YCbCr 525p, 16:9"; break;
    case dpx::k625LineProgressive169AR: tmpstr = "YCbCr 625p, 16:9"; break;
    case dpx::k750LineProgressive169AR:
        tmpstr = "YCbCr 750p, 16:9 (SMPTE 296M)";
        break;
    case dpx::k1125LineProgressive169AR:
        tmpstr = "YCbCr 1125p, 16:9 (SMPTE 274M)";
        break;
    case dpx::k255:
        // don't set the attribute at all
        break;
    default:
        tmpstr = Strutil::sprintf("Undefined %d", (int)m_dpx.header.Signal());
        break;
    }
    if (!tmpstr.empty())
        m_spec.attribute("dpx:Signal", tmpstr);

    // read in user data; don't bother if the buffer is already filled (user
    // data is per-file, not per-element)
    if (m_userBuf.empty() && m_dpx.header.UserSize() != 0
        && m_dpx.header.UserSize() != 0xFFFFFFFF) {
        m_userBuf.resize(m_dpx.header.UserSize());
        m_dpx.ReadUserData(&m_userBuf[0]);
    }
    if (!m_userBuf.empty())
        m_spec.attribute("dpx:UserData",
                         TypeDesc(TypeDesc::UCHAR, m_dpx.header.UserSize()),
                         &m_userBuf[0]);

    // All of the 1-channel encoding options also behave like "rawcolor",
    // not needing color space transformations.
    if (m_spec.nchannels == 1)
        m_rawcolor = true;

    return true;
}



bool
DPXInput::close()
{
    if (m_io_local) {
        // If we allocated our own ioproxy, close it.
        m_io_local.reset();
        m_io = nullptr;
    }
    // N.B. If we were passed an ioproxy from the user (m_io != nullptr, but
    // m_io_local was not set), don't actually close it, it belongs to the
    // caller. And in the case of an ImageCache file, it's possible that the
    // IC won't close this ImageInput until after the owner of the IOProxy
    // destroyed it. So don't mess with it here in close() at all, because
    // we just can't be sure if it's still alive or not.

    init();  // Reset to initial state
    return true;
}



bool
DPXInput::read_native_scanline(int subimage, int miplevel, int y, int z,
                               void* data)
{
    return read_native_scanlines(subimage, miplevel, y, y + 1, z, data);
}



bool
DPXInput::read_native_scanlines(int subimage, int miplevel, int ybegin,
                                int yend, int /*z*/, void* data)
{
    lock_guard lock(m_mutex);
    if (!seek_subimage(subimage, miplevel))
        return false;

    dpx::Block block(0, ybegin - m_spec.y, m_dpx.header.Width() - 1,
                     yend - 1 - m_spec.y);

    if (m_rawcolor) {
        // fast path - just read the scanline in
        if (!m_dpx.ReadBlock(subimage, (unsigned char*)data, block))
            return false;
    } else {
        // read the scanline and convert to RGB
        unsigned char* ptr = (unsigned char*)data;
        int bufsize = dpx::QueryRGBBufferSize(m_dpx.header, subimage, block);
        if (bufsize > 0) {
            m_decodebuf.resize(bufsize);
            ptr = m_decodebuf.data();
        }

        if (!m_dpx.ReadBlock(subimage, ptr, block))
            return false;
        if (!dpx::ConvertToRGB(m_dpx.header, subimage, ptr, data, block))
            return false;
    }

    return true;
}



std::string
DPXInput::get_characteristic_string(dpx::Characteristic c)
{
    switch (c) {
    case dpx::kUserDefined: return "User defined";
    case dpx::kPrintingDensity: return "Printing density";
    case dpx::kLinear: return "Linear";
    case dpx::kLogarithmic: return "Logarithmic";
    case dpx::kUnspecifiedVideo: return "Unspecified video";
    case dpx::kSMPTE274M: return "SMPTE 274M";
    case dpx::kITUR709: return "ITU-R 709-4";
    case dpx::kITUR601: return "ITU-R 601-5 system B or G";
    case dpx::kITUR602: return "ITU-R 601-5 system M";
    case dpx::kNTSCCompositeVideo: return "NTSC composite video";
    case dpx::kPALCompositeVideo: return "PAL composite video";
    case dpx::kZLinear: return "Z depth linear";
    case dpx::kZHomogeneous: return "Z depth homogeneous";
    case dpx::kADX: return "ADX";
    case dpx::kUndefinedCharacteristic:
    default: return "Undefined";
    }
}



std::string
DPXInput::get_descriptor_string(dpx::Descriptor c)
{
    switch (c) {
    case dpx::kUserDefinedDescriptor:
    case dpx::kUserDefined2Comp:
    case dpx::kUserDefined3Comp:
    case dpx::kUserDefined4Comp:
    case dpx::kUserDefined5Comp:
    case dpx::kUserDefined6Comp:
    case dpx::kUserDefined7Comp:
    case dpx::kUserDefined8Comp: return "User defined";
    case dpx::kRed: return "Red";
    case dpx::kGreen: return "Green";
    case dpx::kBlue: return "Blue";
    case dpx::kAlpha: return "Alpha";
    case dpx::kLuma: return "Luma";
    case dpx::kColorDifference: return "Color difference";
    case dpx::kDepth: return "Depth";
    case dpx::kCompositeVideo: return "Composite video";
    case dpx::kRGB: return "RGB";
    case dpx::kRGBA: return "RGBA";
    case dpx::kABGR: return "ABGR";
    case dpx::kCbYCrY: return "CbYCrY";
    case dpx::kCbYACrYA: return "CbYACrYA";
    case dpx::kCbYCr: return "CbYCr";
    case dpx::kCbYCrA: return "CbYCrA";
    //case dpx::kUndefinedDescriptor:
    default: return "Undefined";
    }
}



void
DPXInput::get_keycode_values(int* array)
{
    std::stringstream ss;

    // Manufacturer code
    ss << std::string(m_dpx.header.filmManufacturingIdCode, 2);
    ss >> array[0];
    ss.clear();
    ss.str("");

    // Film type
    ss << std::string(m_dpx.header.filmType, 2);
    ss >> array[1];
    ss.clear();
    ss.str("");

    // Prefix
    ss << std::string(m_dpx.header.prefix, 6);
    ss >> array[2];
    ss.clear();
    ss.str("");

    // Count
    ss << std::string(m_dpx.header.count, 4);
    ss >> array[3];
    ss.clear();
    ss.str("");

    // Perforation Offset
    ss << std::string(m_dpx.header.perfsOffset, 2);
    ss >> array[4];
    ss.clear();
    ss.str("");

    // Format
    std::string format(m_dpx.header.format, 32);
    int& perfsPerFrame = array[5];
    int& perfsPerCount = array[6];

    // default values
    perfsPerFrame = 4;
    perfsPerCount = 64;

    if (format == "8kimax") {
        perfsPerFrame = 15;
        perfsPerCount = 120;
    } else if (format.substr(0, 4) == "2kvv" || format.substr(0, 4) == "4kvv") {
        perfsPerFrame = 8;
    } else if (format == "VistaVision") {
        perfsPerFrame = 8;
    } else if (format.substr(0, 4) == "2k35" || format.substr(0, 4) == "4k35") {
        perfsPerFrame = 4;
    } else if (format == "Full Aperture") {
        perfsPerFrame = 4;
    } else if (format == "Academy") {
        perfsPerFrame = 4;
    } else if (format.substr(0, 7) == "2k3perf"
               || format.substr(0, 7) == "4k3perf") {
        perfsPerFrame = 3;
    } else if (format == "3perf") {
        perfsPerFrame = 3;
    }
}



std::string
DPXInput::get_timecode_string(Imf::TimeCode& tc)
{
    int values[] = { tc.hours(), tc.minutes(), tc.seconds(), tc.frame() };
    std::stringstream ss;
    for (int i = 0; i < 4; i++) {
        std::ostringstream padded;
        padded << std::setw(2) << std::setfill('0') << values[i];
        ss << padded.str();
        if (i != 3) {
            if (i == 2) {
                tc.dropFrame() ? ss << ';' : ss << ':';
            } else {
                ss << ':';
            }
        }
    }
    return ss.str();
}

OIIO_PLUGIN_NAMESPACE_END
