/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2004, 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. // /////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- // // class InputFile // //----------------------------------------------------------------------------- #include "ImfInputFile.h" #include "ImfScanLineInputFile.h" #include "ImfTiledInputFile.h" #include "ImfChannelList.h" #include "ImfMisc.h" #include "ImfStdIO.h" #include "ImfVersion.h" #include "ImfPartType.h" #include "ImfInputPartData.h" #include "ImfMultiPartInputFile.h" #include #include #include "ImathFun.h" #include "IlmThreadMutex.h" #include "Iex.h" #include "half.h" #include #include #include "ImfNamespace.h" OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER using IMATH_NAMESPACE::Box2i; using IMATH_NAMESPACE::divp; using IMATH_NAMESPACE::modp; using ILMTHREAD_NAMESPACE::Mutex; using ILMTHREAD_NAMESPACE::Lock; // // Struct InputFile::Data stores things that will be // needed between calls to readPixels // struct InputFile::Data : public Mutex { Header header; int version; bool isTiled; TiledInputFile * tFile; ScanLineInputFile * sFile; DeepScanLineInputFile * dsFile; LineOrder lineOrder; // the file's lineorder int minY; // data window's min y coord int maxY; // data window's max x coord FrameBuffer tFileBuffer; FrameBuffer * cachedBuffer; CompositeDeepScanLine * compositor; // for loading deep files int cachedTileY; int offset; int numThreads; int partNumber; InputPartData* part; bool multiPartBackwardSupport; MultiPartInputFile* multiPartFile; InputStreamMutex * _streamData; bool _deleteStream; Data (int numThreads); ~Data (); void deleteCachedBuffer(); }; InputFile::Data::Data (int numThreads): isTiled (false), tFile (0), sFile (0), dsFile(0), cachedBuffer (0), compositor(0), cachedTileY (-1), numThreads (numThreads), partNumber (-1), part(NULL), multiPartBackwardSupport (false), multiPartFile (0), _streamData(0), _deleteStream(false) { // empty } InputFile::Data::~Data () { if (tFile) delete tFile; if (sFile) delete sFile; if (dsFile) delete dsFile; if (compositor) delete compositor; deleteCachedBuffer(); if (multiPartBackwardSupport && multiPartFile) delete multiPartFile; } void InputFile::Data::deleteCachedBuffer() { // // Delete the cached frame buffer, and all memory // allocated for the slices in the cached frameBuffer. // if (cachedBuffer) { for (FrameBuffer::Iterator k = cachedBuffer->begin(); k != cachedBuffer->end(); ++k) { Slice &s = k.slice(); switch (s.type) { case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: delete [] (((unsigned int *)s.base) + offset); break; case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: delete [] ((half *)s.base + offset); break; case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: delete [] (((float *)s.base) + offset); break; case NUM_PIXELTYPES : throw(IEX_NAMESPACE::ArgExc("Invalid pixel type")); } } // // delete the cached frame buffer // delete cachedBuffer; cachedBuffer = 0; } } namespace { void bufferedReadPixels (InputFile::Data* ifd, int scanLine1, int scanLine2) { // // bufferedReadPixels reads each row of tiles that intersect the // scan-line range (scanLine1 to scanLine2). The previous row of // tiles is cached in order to prevent redundent tile reads when // accessing scanlines sequentially. // int minY = std::min (scanLine1, scanLine2); int maxY = std::max (scanLine1, scanLine2); if (minY < ifd->minY || maxY > ifd->maxY) { throw IEX_NAMESPACE::ArgExc ("Tried to read scan line outside " "the image file's data window."); } // // The minimum and maximum y tile coordinates that intersect this // scanline range // int minDy = (minY - ifd->minY) / ifd->tFile->tileYSize(); int maxDy = (maxY - ifd->minY) / ifd->tFile->tileYSize(); // // Figure out which one is first in the file so we can read without seeking // int yStart, yEnd, yStep; if (ifd->lineOrder == DECREASING_Y) { yStart = maxDy; yEnd = minDy - 1; yStep = -1; } else { yStart = minDy; yEnd = maxDy + 1; yStep = 1; } // // the number of pixels in a row of tiles // Box2i levelRange = ifd->tFile->dataWindowForLevel(0); // // Read the tiles into our temporary framebuffer and copy them into // the user's buffer // for (int j = yStart; j != yEnd; j += yStep) { Box2i tileRange = ifd->tFile->dataWindowForTile (0, j, 0); int minYThisRow = std::max (minY, tileRange.min.y); int maxYThisRow = std::min (maxY, tileRange.max.y); if (j != ifd->cachedTileY) { // // We don't have any valid buffered info, so we need to read in // from the file. // ifd->tFile->readTiles (0, ifd->tFile->numXTiles (0) - 1, j, j); ifd->cachedTileY = j; } // // Copy the data from our cached framebuffer into the user's // framebuffer. // for (FrameBuffer::ConstIterator k = ifd->cachedBuffer->begin(); k != ifd->cachedBuffer->end(); ++k) { Slice fromSlice = k.slice(); // slice to write from Slice toSlice = ifd->tFileBuffer[k.name()]; // slice to write to char *fromPtr, *toPtr; int size = pixelTypeSize (toSlice.type); int xStart = levelRange.min.x; int yStart = minYThisRow; while (modp (xStart, toSlice.xSampling) != 0) ++xStart; while (modp (yStart, toSlice.ySampling) != 0) ++yStart; for (int y = yStart; y <= maxYThisRow; y += toSlice.ySampling) { // // Set the pointers to the start of the y scanline in // this row of tiles // fromPtr = fromSlice.base + (y - tileRange.min.y) * fromSlice.yStride + xStart * fromSlice.xStride; toPtr = toSlice.base + divp (y, toSlice.ySampling) * toSlice.yStride + divp (xStart, toSlice.xSampling) * toSlice.xStride; // // Copy all pixels for the scanline in this row of tiles // for (int x = xStart; x <= levelRange.max.x; x += toSlice.xSampling) { for (int i = 0; i < size; ++i) toPtr[i] = fromPtr[i]; fromPtr += fromSlice.xStride * toSlice.xSampling; toPtr += toSlice.xStride; } } } } } } // namespace InputFile::InputFile (const char fileName[], int numThreads): _data (new Data (numThreads)) { _data->_streamData = NULL; _data->_deleteStream=true; OPENEXR_IMF_INTERNAL_NAMESPACE::IStream* is = 0; try { is = new StdIFStream (fileName); readMagicNumberAndVersionField(*is, _data->version); // // compatibility to read multipart file. // if (isMultiPart(_data->version)) { compatibilityInitialize(*is); } else { _data->_streamData = new InputStreamMutex(); _data->_streamData->is = is; _data->header.readFrom (*_data->_streamData->is, _data->version); // fix type attribute in single part regular image types // (may be wrong if an old version of OpenEXR converts // a tiled image to scanline or vice versa) if(!isNonImage(_data->version) && !isMultiPart(_data->version) && _data->header.hasType()) { _data->header.setType(isTiled(_data->version) ? TILEDIMAGE : SCANLINEIMAGE); } _data->header.sanityCheck (isTiled (_data->version)); initialize(); } } catch (IEX_NAMESPACE::BaseExc &e) { if (is) delete is; if ( _data && !_data->multiPartBackwardSupport && _data->_streamData) { delete _data->_streamData; _data->_streamData=NULL; } if (_data) delete _data; _data=NULL; REPLACE_EXC (e, "Cannot read image file " "\"" << fileName << "\". " << e); throw; } catch (...) { if (is) delete is; if (_data && !_data->multiPartBackwardSupport && _data->_streamData) { delete _data->_streamData; } if (_data) delete _data; throw; } } InputFile::InputFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, int numThreads): _data (new Data (numThreads)) { _data->_streamData=NULL; _data->_deleteStream=false; try { readMagicNumberAndVersionField(is, _data->version); // // Backward compatibility to read multpart file. // if (isMultiPart(_data->version)) { compatibilityInitialize(is); } else { _data->_streamData = new InputStreamMutex(); _data->_streamData->is = &is; _data->header.readFrom (*_data->_streamData->is, _data->version); // fix type attribute in single part regular image types // (may be wrong if an old version of OpenEXR converts // a tiled image to scanline or vice versa) if(!isNonImage(_data->version) && !isMultiPart(_data->version) && _data->header.hasType()) { _data->header.setType(isTiled(_data->version) ? TILEDIMAGE : SCANLINEIMAGE); } _data->header.sanityCheck (isTiled (_data->version)); initialize(); } } catch (IEX_NAMESPACE::BaseExc &e) { if (_data && !_data->multiPartBackwardSupport && _data->_streamData) delete _data->_streamData; if (_data) delete _data; _data=NULL; REPLACE_EXC (e, "Cannot read image file " "\"" << is.fileName() << "\". " << e); throw; } catch (...) { if (_data && !_data->multiPartBackwardSupport && _data->_streamData) delete _data->_streamData; if (_data) delete _data; _data=NULL; throw; } } InputFile::InputFile (InputPartData* part) : _data (new Data (part->numThreads)) { _data->_deleteStream=false; multiPartInitialize (part); } void InputFile::compatibilityInitialize (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is) { is.seekg(0); // // Construct a MultiPartInputFile, initialize InputFile // with the part 0 data. // (TODO) may want to have a way to set the reconstruction flag. // _data->multiPartBackwardSupport = true; _data->multiPartFile = new MultiPartInputFile(is, _data->numThreads); InputPartData* part = _data->multiPartFile->getPart(0); multiPartInitialize (part); } void InputFile::multiPartInitialize (InputPartData* part) { _data->_streamData = part->mutex; _data->version = part->version; _data->header = part->header; _data->partNumber = part->partNumber; _data->part = part; initialize(); } void InputFile::initialize () { if (!_data->part) { if(_data->header.hasType() && _data->header.type()==DEEPSCANLINE) { _data->isTiled=false; const Box2i &dataWindow = _data->header.dataWindow(); _data->minY = dataWindow.min.y; _data->maxY = dataWindow.max.y; _data->dsFile = new DeepScanLineInputFile (_data->header, _data->_streamData->is, _data->version, _data->numThreads); _data->compositor = new CompositeDeepScanLine; _data->compositor->addSource(_data->dsFile); } else if (isTiled (_data->version)) { _data->isTiled = true; _data->lineOrder = _data->header.lineOrder(); // // Save the dataWindow information // const Box2i &dataWindow = _data->header.dataWindow(); _data->minY = dataWindow.min.y; _data->maxY = dataWindow.max.y; _data->tFile = new TiledInputFile (_data->header, _data->_streamData->is, _data->version, _data->numThreads); } else if(!_data->header.hasType() || _data->header.type()==SCANLINEIMAGE) { _data->sFile = new ScanLineInputFile (_data->header, _data->_streamData->is, _data->numThreads); }else{ // type set but not recognised THROW(IEX_NAMESPACE::ArgExc, "InputFile cannot handle parts of type " << _data->header.type()); } } else { if(_data->header.hasType() && _data->header.type()==DEEPSCANLINE) { _data->isTiled=false; const Box2i &dataWindow = _data->header.dataWindow(); _data->minY = dataWindow.min.y; _data->maxY = dataWindow.max.y; _data->dsFile = new DeepScanLineInputFile (_data->part); _data->compositor = new CompositeDeepScanLine; _data->compositor->addSource(_data->dsFile); } else if (isTiled (_data->header.type())) { _data->isTiled = true; _data->lineOrder = _data->header.lineOrder(); // // Save the dataWindow information // const Box2i &dataWindow = _data->header.dataWindow(); _data->minY = dataWindow.min.y; _data->maxY = dataWindow.max.y; _data->tFile = new TiledInputFile (_data->part); } else if(!_data->header.hasType() || _data->header.type()==SCANLINEIMAGE) { _data->sFile = new ScanLineInputFile (_data->part); }else{ THROW(IEX_NAMESPACE::ArgExc, "InputFile cannot handle parts of type " << _data->header.type()); } } } #include InputFile::~InputFile () { if (_data->_deleteStream) delete _data->_streamData->is; // unless this file was opened via the multipart API, // delete the streamData object too if (_data->partNumber==-1 && _data->_streamData) delete _data->_streamData; if (_data) delete _data; } const char * InputFile::fileName () const { return _data->_streamData->is->fileName(); } const Header & InputFile::header () const { return _data->header; } int InputFile::version () const { return _data->version; } void InputFile::setFrameBuffer (const FrameBuffer &frameBuffer) { if (_data->isTiled) { Lock lock (*_data); // // We must invalidate the cached buffer if the new frame // buffer has a different set of channels than the old // frame buffer, or if the type of a channel has changed. // const FrameBuffer &oldFrameBuffer = _data->tFileBuffer; FrameBuffer::ConstIterator i = oldFrameBuffer.begin(); FrameBuffer::ConstIterator j = frameBuffer.begin(); while (i != oldFrameBuffer.end() && j != frameBuffer.end()) { if (strcmp (i.name(), j.name()) || i.slice().type != j.slice().type) break; ++i; ++j; } if (i != oldFrameBuffer.end() || j != frameBuffer.end()) { // // Invalidate the cached buffer. // _data->deleteCachedBuffer (); _data->cachedTileY = -1; // // Create new a cached frame buffer. It can hold a single // row of tiles. The cached buffer can be reused for each // row of tiles because we set the yTileCoords parameter of // each Slice to true. // const Box2i &dataWindow = _data->header.dataWindow(); _data->cachedBuffer = new FrameBuffer(); _data->offset = dataWindow.min.x; int tileRowSize = (dataWindow.max.x - dataWindow.min.x + 1) * _data->tFile->tileYSize(); for (FrameBuffer::ConstIterator k = frameBuffer.begin(); k != frameBuffer.end(); ++k) { Slice s = k.slice(); switch (s.type) { case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: _data->cachedBuffer->insert (k.name(), Slice (UINT, (char *)(new unsigned int[tileRowSize] - _data->offset), sizeof (unsigned int), sizeof (unsigned int) * _data->tFile->levelWidth(0), 1, 1, s.fillValue, false, true)); break; case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: _data->cachedBuffer->insert (k.name(), Slice (HALF, (char *)(new half[tileRowSize] - _data->offset), sizeof (half), sizeof (half) * _data->tFile->levelWidth(0), 1, 1, s.fillValue, false, true)); break; case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: _data->cachedBuffer->insert (k.name(), Slice (OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT, (char *)(new float[tileRowSize] - _data->offset), sizeof(float), sizeof(float) * _data->tFile->levelWidth(0), 1, 1, s.fillValue, false, true)); break; default: throw IEX_NAMESPACE::ArgExc ("Unknown pixel data type."); } } _data->tFile->setFrameBuffer (*_data->cachedBuffer); } _data->tFileBuffer = frameBuffer; } else if(_data->compositor) { _data->compositor->setFrameBuffer(frameBuffer); }else { _data->sFile->setFrameBuffer(frameBuffer); _data->tFileBuffer = frameBuffer; } } const FrameBuffer & InputFile::frameBuffer () const { if(_data->compositor) { return _data->compositor->frameBuffer(); } else if(_data->isTiled) { Lock lock (*_data); return _data->tFileBuffer; } else { return _data->sFile->frameBuffer(); } } bool InputFile::isComplete () const { if (_data->dsFile) return _data->dsFile->isComplete(); else if (_data->isTiled) return _data->tFile->isComplete(); else return _data->sFile->isComplete(); } bool InputFile::isOptimizationEnabled() const { if(_data->sFile) { return _data->sFile->isOptimizationEnabled(); }else{ return false; } } void InputFile::readPixels (int scanLine1, int scanLine2) { if (_data->compositor) { _data->compositor->readPixels(scanLine1,scanLine2); } else if (_data->isTiled) { Lock lock (*_data); bufferedReadPixels (_data, scanLine1, scanLine2); } else { _data->sFile->readPixels (scanLine1, scanLine2); } } void InputFile::readPixels (int scanLine) { readPixels (scanLine, scanLine); } void InputFile::rawPixelData (int firstScanLine, const char *&pixelData, int &pixelDataSize) { try { if (_data->dsFile) { throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " "from a deep image."); } else if (_data->isTiled) { throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " "from a tiled image."); } _data->sFile->rawPixelData (firstScanLine, pixelData, pixelDataSize); } catch (IEX_NAMESPACE::BaseExc &e) { REPLACE_EXC (e, "Error reading pixel data from image " "file \"" << fileName() << "\". " << e); throw; } } void InputFile::rawTileData (int &dx, int &dy, int &lx, int &ly, const char *&pixelData, int &pixelDataSize) { try { if (!_data->isTiled) { throw IEX_NAMESPACE::ArgExc ("Tried to read a raw tile " "from a scanline-based image."); } _data->tFile->rawTileData (dx, dy, lx, ly, pixelData, pixelDataSize); } catch (IEX_NAMESPACE::BaseExc &e) { REPLACE_EXC (e, "Error reading tile data from image " "file \"" << fileName() << "\". " << e); throw; } } TiledInputFile* InputFile::tFile() { if (!_data->isTiled) { throw IEX_NAMESPACE::ArgExc ("Cannot get a TiledInputFile pointer " "from an InputFile that is not tiled."); } return _data->tFile; } OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT