/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2011, Industrial Light & Magic, a division of Lucas // Digital Ltd. LLC // // 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 Industrial Light & Magic nor the names of // its contributors 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 "ImfMultiPartInputFile.h" #include "ImfTimeCodeAttribute.h" #include "ImfChromaticitiesAttribute.h" #include "ImfBoxAttribute.h" #include "ImfFloatAttribute.h" #include "ImfStdIO.h" #include "ImfTileOffsets.h" #include "ImfMisc.h" #include "ImfTiledMisc.h" #include "ImfInputStreamMutex.h" #include "ImfInputPartData.h" #include "ImfPartType.h" #include "ImfInputFile.h" #include "ImfScanLineInputFile.h" #include "ImfTiledInputFile.h" #include "ImfDeepScanLineInputFile.h" #include "ImfDeepTiledInputFile.h" #include "ImfVersion.h" #include #include #include #include #include #include OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER using ILMTHREAD_NAMESPACE::Mutex; using ILMTHREAD_NAMESPACE::Lock; using IMATH_NAMESPACE::Box2i; using std::vector; using std::map; using std::set; using std::string; namespace { // Controls whether we error out in the event of shared attribute // inconsistency in the input file static const bool strictSharedAttribute = true; } struct MultiPartInputFile::Data: public InputStreamMutex { int version; // Version of this file. bool deleteStream; // If we should delete the stream during destruction. vector parts; // Data to initialize Output files. int numThreads; // Number of threads bool reconstructChunkOffsetTable; // If we should reconstruct // the offset table if it's broken. std::map _inputFiles; std::vector
_headers; void chunkOffsetReconstruction(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, const std::vector& parts); void readChunkOffsetTables(bool reconstructChunkOffsetTable); bool checkSharedAttributesValues(const Header & src, const Header & dst, std::vector & conflictingAttributes) const; TileOffsets* createTileOffsets(const Header& header); InputPartData* getPart(int partNumber); Data (bool deleteStream, int numThreads, bool reconstructChunkOffsetTable): InputStreamMutex(), deleteStream (deleteStream), numThreads (numThreads), reconstructChunkOffsetTable(reconstructChunkOffsetTable) { } ~Data() { if (deleteStream) delete is; for (size_t i = 0; i < parts.size(); i++) delete parts[i]; } template T* createInputPartT(int partNumber) { } }; MultiPartInputFile::MultiPartInputFile(const char fileName[], int numThreads, bool reconstructChunkOffsetTable): _data(new Data(true, numThreads, reconstructChunkOffsetTable)) { try { _data->is = new StdIFStream (fileName); initialize(); } catch (IEX_NAMESPACE::BaseExc &e) { delete _data; REPLACE_EXC (e, "Cannot read image file " "\"" << fileName << "\". " << e.what()); throw; } catch (...) { delete _data; throw; } } MultiPartInputFile::MultiPartInputFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int numThreads, bool reconstructChunkOffsetTable): _data(new Data(false, numThreads, reconstructChunkOffsetTable)) { try { _data->is = &is; initialize(); } catch (IEX_NAMESPACE::BaseExc &e) { delete _data; REPLACE_EXC (e, "Cannot read image file " "\"" << is.fileName() << "\". " << e.what()); throw; } catch (...) { delete _data; throw; } } template T* MultiPartInputFile::getInputPart(int partNumber) { Lock lock(*_data); if (_data->_inputFiles.find(partNumber) == _data->_inputFiles.end()) { T* file = new T(_data->getPart(partNumber)); _data->_inputFiles.insert(std::make_pair(partNumber, (GenericInputFile*) file)); return file; } else return (T*) _data->_inputFiles[partNumber]; } template InputFile* MultiPartInputFile::getInputPart(int); template TiledInputFile* MultiPartInputFile::getInputPart(int); template DeepScanLineInputFile* MultiPartInputFile::getInputPart(int); template DeepTiledInputFile* MultiPartInputFile::getInputPart(int); InputPartData* MultiPartInputFile::getPart(int partNumber) { return _data->getPart(partNumber); } const Header & MultiPartInputFile::header(int n) const { return _data->_headers[n]; } MultiPartInputFile::~MultiPartInputFile() { for (map::iterator it = _data->_inputFiles.begin(); it != _data->_inputFiles.end(); it++) { delete it->second; } delete _data; } bool MultiPartInputFile::Data::checkSharedAttributesValues(const Header & src, const Header & dst, vector & conflictingAttributes) const { conflictingAttributes.clear(); bool conflict = false; // // Display Window // if (src.displayWindow() != dst.displayWindow()) { conflict = true; conflictingAttributes.push_back ("displayWindow"); } // // Pixel Aspect Ratio // if (src.pixelAspectRatio() != dst.pixelAspectRatio()) { conflict = true; conflictingAttributes.push_back ("pixelAspectRatio"); } // // Timecode // const TimeCodeAttribute * srcTimeCode = src.findTypedAttribute< TimeCodeAttribute> (TimeCodeAttribute::staticTypeName()); const TimeCodeAttribute * dstTimeCode = dst.findTypedAttribute< TimeCodeAttribute> (TimeCodeAttribute::staticTypeName()); if (dstTimeCode) { if ( (srcTimeCode && (srcTimeCode->value() != dstTimeCode->value())) || (!srcTimeCode)) { conflict = true; conflictingAttributes.push_back (TimeCodeAttribute::staticTypeName()); } } // // Chromaticities // const ChromaticitiesAttribute * srcChrom = src.findTypedAttribute< ChromaticitiesAttribute> (ChromaticitiesAttribute::staticTypeName()); const ChromaticitiesAttribute * dstChrom = dst.findTypedAttribute< ChromaticitiesAttribute> (ChromaticitiesAttribute::staticTypeName()); if (dstChrom) { if ( (srcChrom && (srcChrom->value() != dstChrom->value())) || (!srcChrom)) { conflict = true; conflictingAttributes.push_back (ChromaticitiesAttribute::staticTypeName()); } } return conflict; } void MultiPartInputFile::initialize() { readMagicNumberAndVersionField(*_data->is, _data->version); bool multipart = isMultiPart(_data->version); bool tiled = isTiled(_data->version); // // Multipart files don't have and shouldn't have the tiled bit set. // if (tiled && multipart) throw IEX_NAMESPACE::InputExc ("Multipart files cannot have the tiled bit set"); int pos = 0; while (true) { Header header; header.readFrom(*_data->is, _data->version); // // If we read nothing then we stop reading. // if (header.readsNothing()) { pos++; break; } _data->_headers.push_back(header); if(multipart == false) break; } // // Perform usual check on headers. // for (size_t i = 0; i < _data->_headers.size(); i++) { // // Silently invent a type if the file is a single part regular image. // if( _data->_headers[i].hasType() == false ) { if(multipart) throw IEX_NAMESPACE::ArgExc ("Every header in a multipart file should have a type"); _data->_headers[i].setType(tiled ? TILEDIMAGE : SCANLINEIMAGE); } else { // // Silently fix the header type if it's wrong // (happens when a regular Image file written by EXR_2.0 is rewritten by an older library, // so doesn't effect deep image types) // if(!multipart && !isNonImage(_data->version)) { _data->_headers[i].setType(tiled ? TILEDIMAGE : SCANLINEIMAGE); } } if( _data->_headers[i].hasName() == false ) { if(multipart) throw IEX_NAMESPACE::ArgExc ("Every header in a multipart file should have a name"); } if (isTiled(_data->_headers[i].type())) _data->_headers[i].sanityCheck(true, multipart); else _data->_headers[i].sanityCheck(false, multipart); } // // Check name uniqueness. // if (multipart) { set names; for (size_t i = 0; i < _data->_headers.size(); i++) { if (names.find(_data->_headers[i].name()) != names.end()) { throw IEX_NAMESPACE::InputExc ("Header name " + _data->_headers[i].name() + " is not a unique name."); } names.insert(_data->_headers[i].name()); } } // // Check shared attributes compliance. // if (multipart && strictSharedAttribute) { for (size_t i = 1; i < _data->_headers.size(); i++) { vector attrs; if (_data->checkSharedAttributesValues (_data->_headers[0], _data->_headers[i], attrs)) { string attrNames; for (size_t j=0; j_headers[i].name() + " has non-conforming shared attributes: "+ attrNames); } } } // // Create InputParts and read chunk offset tables. // for (size_t i = 0; i < _data->_headers.size(); i++) _data->parts.push_back( new InputPartData(_data, _data->_headers[i], i, _data->numThreads, _data->version)); _data->readChunkOffsetTables(_data->reconstructChunkOffsetTable); } TileOffsets* MultiPartInputFile::Data::createTileOffsets(const Header& header) { // // Get the dataWindow information // const Box2i &dataWindow = header.dataWindow(); int minX = dataWindow.min.x; int maxX = dataWindow.max.x; int minY = dataWindow.min.y; int maxY = dataWindow.max.y; // // Precompute level and tile information // int* numXTiles; int* numYTiles; int numXLevels, numYLevels; TileDescription tileDesc = header.tileDescription(); precalculateTileInfo (tileDesc, minX, maxX, minY, maxY, numXTiles, numYTiles, numXLevels, numYLevels); TileOffsets* tileOffsets = new TileOffsets (tileDesc.mode, numXLevels, numYLevels, numXTiles, numYTiles); delete [] numXTiles; delete [] numYTiles; return tileOffsets; } void MultiPartInputFile::Data::chunkOffsetReconstruction(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, const vector& parts) { // // Reconstruct broken chunk offset tables. Stop once we received any exception. // Int64 position = is.tellg(); // // check we understand all the parts available: if not, we cannot continue // exceptions thrown here should trickle back up to the constructor // for (size_t i = 0; i < parts.size(); i++) { Header& header=parts[i]->header; // // do we have a valid type entry? // we only need them for true multipart files or single part non-image (deep) files // if(!header.hasType() && (isMultiPart(version) || isNonImage(version))) { throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with missing type"); } if(!isSupportedType(header.type())) { throw IEX_NAMESPACE::ArgExc("cannot reconstruct incomplete file: part with unknown type "+header.type()); } } // how many chunks should we read? We should stop when we reach the end size_t total_chunks = 0; // for tiled-based parts, array of (pointers to) tileOffsets objects // to create mapping between tile coordinates and chunk table indices vector tileOffsets(parts.size()); // for scanline-based parts, number of scanlines in each part vector rowsizes(parts.size()); for(size_t i = 0 ; i < parts.size() ; i++) { total_chunks += parts[i]->chunkOffsets.size(); if (isTiled(parts[i]->header.type())) { tileOffsets[i] = createTileOffsets(parts[i]->header); }else{ tileOffsets[i] = NULL; // (TODO) fix this so that it doesn't need to be revised for future compression types. switch(parts[i]->header.compression()) { case DWAB_COMPRESSION : rowsizes[i] = 256; break; case PIZ_COMPRESSION : case B44_COMPRESSION : case B44A_COMPRESSION : case DWAA_COMPRESSION : rowsizes[i]=32; break; case ZIP_COMPRESSION : case PXR24_COMPRESSION : rowsizes[i]=16; break; case ZIPS_COMPRESSION : case RLE_COMPRESSION : case NO_COMPRESSION : rowsizes[i]=1; break; default : throw(IEX_NAMESPACE::ArgExc("Unknown compression method in chunk offset reconstruction")); } } } try { // // // Int64 chunk_start = position; for (size_t i = 0; i < total_chunks ; i++) { // // do we have a part number? // int partNumber = 0; if(isMultiPart(version)) { OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, partNumber); } if(partNumber<0 || partNumber>int(parts.size())) { // bail here - bad part number throw int(); } Header& header = parts[partNumber]->header; // size of chunk NOT including multipart field Int64 size_of_chunk=0; if (isTiled(header.type())) { // // // int tilex,tiley,levelx,levely; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, tilex); OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, tiley); OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, levelx); OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, levely); //std::cout << "chunk_start for " << tilex <<',' << tiley << ',' << levelx << ' ' << levely << ':' << chunk_start << std::endl; if(!tileOffsets[partNumber]) { // this shouldn't actually happen - we should have allocated a valid // tileOffsets for any part which isTiled throw int(); } if(!tileOffsets[partNumber]->isValidTile(tilex,tiley,levelx,levely)) { //std::cout << "invalid tile : aborting\n"; throw int(); } (*tileOffsets[partNumber])(tilex,tiley,levelx,levely)=chunk_start; // compute chunk sizes - different procedure for deep tiles and regular // ones if(header.type()==DEEPTILE) { Int64 packed_offset; Int64 packed_sample; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, packed_offset); OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, packed_sample); //add 40 byte header to packed sizes (tile coordinates, packed sizes, unpacked size) size_of_chunk=packed_offset+packed_sample+40; } else { // regular image has 20 bytes of header, 4 byte chunksize; int chunksize; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, chunksize); size_of_chunk=chunksize+20; } } else { int y_coordinate; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, y_coordinate); y_coordinate -= header.dataWindow().min.y; y_coordinate /= rowsizes[partNumber]; if(y_coordinate < 0 || y_coordinate >= int(parts[partNumber]->chunkOffsets.size())) { //std::cout << "aborting reconstruction: bad data " << y_coordinate << endl; //bail to exception catcher: broken scanline throw int(); } parts[partNumber]->chunkOffsets[y_coordinate]=chunk_start; //std::cout << "chunk_start for " << y_coordinate << ':' << chunk_start << std::endl; if(header.type()==DEEPSCANLINE) { Int64 packed_offset; Int64 packed_sample; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, packed_offset); OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, packed_sample); size_of_chunk=packed_offset+packed_sample+28; } else { int chunksize; OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (is, chunksize); size_of_chunk=chunksize+8; } } if(isMultiPart(version)) { chunk_start+=4; } chunk_start+=size_of_chunk; //std::cout << " next chunk +"<header,false); parts[i]->chunkOffsets.resize(chunkOffsetTableSize); for (int j = 0; j < chunkOffsetTableSize; j++) OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read (*is, parts[i]->chunkOffsets[j]); // // Check chunk offsets, reconstruct if broken. // At first we assume the table is complete. // parts[i]->completed = true; for (int j = 0; j < chunkOffsetTableSize; j++) { if (parts[i]->chunkOffsets[j] <= 0) { brokenPartsExist = true; parts[i]->completed = false; break; } } } if (brokenPartsExist && reconstructChunkOffsetTable) chunkOffsetReconstruction(*is, parts); } int MultiPartInputFile::version() const { return _data->version; } bool MultiPartInputFile::partComplete(int part) const { return _data->parts[part]->completed; } int MultiPartInputFile::parts() const { return int(_data->_headers.size()); } OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT