// exrReaderDeep.cpp /////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2011 The Foundry Visionmongers Ltd. All Rights Reserved. // Portions contributed and copyright held by others as indicated. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided with // the distribution. // // * Neither the name of The Foundry Visionmongers nor any other contributors // to this software may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DDImage/DeepReader.h" #include "DDImage/DeepOp.h" #include "DDImage/Thread.h" #include "exrGeneral.h" using namespace DD::Image; class exrDeepReaderFormat : public DeepReaderFormat { bool _doNotAttachPrefix; public: exrDeepReaderFormat() { _doNotAttachPrefix = false; } bool doNotAttachPrefix() const { return _doNotAttachPrefix; } void knobs(Knob_Callback f) { Bool_knob(f, &_doNotAttachPrefix, "noprefix", "do not attach prefix"); Tooltip(f, "By default the 'exr' prefix is attached to metadata keys to make it distinct from other metadata in the tree. Enable this option to read the metadata 'as is'' without attaching the exr prefix."); } void append(Hash& hash) { hash.append(_doNotAttachPrefix); } }; /** * OpenEXR2 reader for deep data. * * Missing features, to be adapted from the existing EXR reader: * stereo */ class exrReaderDeep : public DeepReader { std::string _filename; ChannelSet _decodeChannels; ChannelMap _decodeChannelMap; Imf::MultiPartInputFile *_file; Imf::DeepScanLineInputPart *_part; int _partNumber; std::map _chans; std::map _chanTypes; MetaData::Bundle _meta; Lock _lock; void createChannelMap(const Imf::Header& header); const char* chanName(Channel chan) { return _chans[chan].c_str(); } bool decodeLine(Imf::DeepScanLineInputPart& part, int exrY, const Box& box, const ChannelSet& reqChannels, DeepOutputPlane& plane); void setMetaData( const Imf::Header& header, DD::Image::MetaData::Bundle& meta, bool doNotAttachPrefix ); public: exrReaderDeep(DeepReaderOwner* iop, const std::string& filename); virtual ~exrReaderDeep(); virtual bool doDeepEngine(Box box, const ChannelSet& reqChannels, DeepOutputPlane& plane); const MetaData::Bundle& fetchMetaData(const char* key); }; exrReaderDeep::exrReaderDeep(DeepReaderOwner* iop, const std::string& filename) : DeepReader(iop), _filename(filename) { _file = 0; _part = 0; exrDeepReaderFormat *exrOptions = dynamic_cast( iop->handler() ); bool doNotAttachPrefix = exrOptions ? exrOptions->doNotAttachPrefix() : false; try { _file = new Imf::MultiPartInputFile( filename.c_str() ); std::string view( iop->readerOutputContext().viewname() ); int partNumber = -1; for(int i=0; i < _file->parts(); i++) { const Imf::Header& header = _file->header(i); if ( i == 0 ) setMetaData( header, _metaData, doNotAttachPrefix ); // read 'global' metadata from first part // look for a part with a type set to DEEPSCANLINE if ( ! header.hasType() || header.type() != Imf::DEEPSCANLINE ) continue; if( header.hasView() ) { if( view == header.view() ) { partNumber = i; break; } else { // wrong view, but it'll do if all else fails partNumber = i; } } else { // no view mentioned - if we wanted main, that's exactly right if( view == "main" ) { partNumber = i; break; } else { // no view in file - if we aren't using an earlier part, use this one if ( partNumber == -1 ) partNumber = i; } } } if( partNumber == -1) { throw Iex::InputExc("no deep data found in file"); } _partNumber = partNumber; _part = new Imf::DeepScanLineInputPart( *_file, _partNumber); const Imf::Header& header = _part->header(); createChannelMap(header); // read part metadata - overrides global metadata setMetaData( header, _metaData, doNotAttachPrefix ); Box displayBox = boxToBox(header.displayWindow(), header.displayWindow()); Box dataBox = boxToBox(header.dataWindow(), header.displayWindow()); setInfo(displayBox.r(), displayBox.h(), OutputContext(), _decodeChannels ); _deepInfo.box().set(dataBox); } catch ( Iex::BaseExc& e ) { _owner->readerInternalError( e.what() ); } } exrReaderDeep::~exrReaderDeep() { delete _file; delete _part; } /** * decode the ofx channel names to Nuke channel numbers and * names */ void exrReaderDeep::createChannelMap(const Imf::Header& header) { for (Imf::ChannelList::ConstIterator it = header.channels().begin(); it != header.channels().end(); it++) { const char* chanName = it.name(); Channel channel = getExrChannel(chanName); _chans[channel] = chanName; _decodeChannels += channel; } _decodeChannelMap = ChannelMap(_decodeChannels); } bool exrReaderDeep::decodeLine(Imf::DeepScanLineInputPart& part, int exrY, const Box& box, const ChannelSet& reqChannels, DeepOutputPlane& plane) { const Imf::Header& header = part.header(); const int dataWid = header.dataWindow().size().x + 1; const int dataX = header.dataWindow().min.x; if (exrY < header.dataWindow().min.y || exrY > header.dataWindow().max.y) return false; std::vector sampleCounts(dataWid, 0); /// set the slice for the sample counts Imf::DeepFrameBuffer frameBuffer; frameBuffer.insertSampleCountSlice(Imf::Slice(Imf::UINT, (char*)(&sampleCounts[0] - dataX), sizeof(unsigned), 0)); unsigned chanCount = _decodeChannelMap.size(); std::vector > samples; samples.reserve(chanCount); // set up the slices for the actual data. Note these pointers // don't actually point anywhere yet, they can't be filled in // until we have done readPixelSampleCounts (but they need to be // present for setFrameBuffer) // // Due to an apparent bug in the EXR library, we need to decode // ALL the channels in the file, even if we aren't interested // in them. foreach(z, _decodeChannels) { samples.push_back(std::vector(dataWid)); frameBuffer.insert(chanName(z), Imf::DeepSlice(Imf::FLOAT, (char *)(&samples.back()[0] - dataX), sizeof(const float*), // xstride 0, // ystride sizeof(float) * chanCount)); // samplestride } std::vector data; { Guard g(_lock); part.setFrameBuffer(frameBuffer); part.readPixelSampleCounts(exrY, exrY); // get the total number of samples unsigned sampleCountSum = 0; for (unsigned i = 0; i < sampleCounts.size(); i++) sampleCountSum += sampleCounts[i]; // if there were no samples, skip out if (sampleCountSum == 0) { return false; } // allocate the actual data for the samples, and then go back and // set the samples vectors that the framebuffer is pointing at // to point to spans within this. data.resize(sampleCountSum * chanCount, 0); foreach(z, _decodeChannels) { const int chanNo = _decodeChannelMap.chanNo(z); const float* ptr = &data[0] + chanNo; for (int x = 0; x < dataWid; x++) { samples[chanNo][x] = ptr; ptr += chanCount * sampleCounts[x]; } } // read the actual data part.readPixels(exrY); } for (int x = box.x(); x < box.r(); x++) { const unsigned lineX = x - dataX; if (lineX >= sampleCounts.size() || sampleCounts[lineX] == 0) plane.addHole(); // if we're out of range, add a hole (no data) else { DeepOutPixel outPixel; const unsigned sampleCount = sampleCounts[lineX]; // copy data from the unpacked buffer into the output plane. // in some future version it might be nice to unpack directly // into the output plane's buffer. Possibly need some API changes // for this. for (unsigned sampleNo = 0; sampleNo < sampleCount; sampleNo++) foreach(z, reqChannels) { const int chanNo = _decodeChannelMap.chanNo(z); if (_decodeChannelMap.contains(z)) { outPixel.push_back(samples[chanNo][lineX][sampleNo * chanCount]); } else outPixel.push_back(0); } plane.addPixel(outPixel); } } return true; } void exrReaderDeep::setMetaData( const Imf::Header& header, DD::Image::MetaData::Bundle& meta, bool doNotAttachPrefix) { std::map pixelTypes; for (Imf::ChannelList::ConstIterator it = header.channels().begin(); it != header.channels().end(); it++) { Channel channel = getExrChannel( it.name() ); if ( channel != Chan_DeepBack && channel != Chan_DeepFront ) pixelTypes[ it.channel().type ]++; } if (pixelTypes[Imf::FLOAT] > 0) { meta.setData(MetaData::DEPTH, MetaData::DEPTH_FLOAT); } else if (pixelTypes[Imf::HALF] > 0) { meta.setData(MetaData::DEPTH, MetaData::DEPTH_HALF); } exrHeaderToMetadata( header, meta, doNotAttachPrefix ); } bool exrReaderDeep::doDeepEngine(Box box, const ChannelSet& reqChannels, DeepOutputPlane& plane) { const Imf::Header& header = _part->header(); plane = DeepOutputPlane(reqChannels, box); try { for (int y = box.y(); y < box.t(); y++) { int exrY = lineToLine(y, header.displayWindow()); bool decoded = decodeLine(*_part, exrY, box, reqChannels, plane); if (!decoded) for (int x = box.x(); x < box.r(); x++) plane.addHole(); } } catch ( Iex::BaseExc& e ) { _op->error( e.what() ); return false; } return true; } static DeepReader* build(DeepReaderOwner* iop, const std::string& fn) { return new exrReaderDeep(iop, fn); } static DeepReaderFormat* buildFormat(DeepReaderOwner* op) { return new exrDeepReaderFormat(); } static const DeepReader::Description d("exr\0", build, buildFormat);