/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2002, 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined PLATFORM_WIN32 && _MSC_VER < 1300 namespace { template inline T min (const T &a, const T &b) { return (a <= b) ? a : b; } template inline T max (const T &a, const T &b) { return (a >= b) ? a : b; } } #endif namespace Imf { using Imath::Box2i; using Imath::divp; using Imath::modp; using std::string; using std::vector; using std::ifstream; namespace { struct InSliceInfo { PixelType typeInFrameBuffer; PixelType typeInFile; char * base; size_t xStride; size_t yStride; int xSampling; int ySampling; bool fill; bool skip; double fillValue; InSliceInfo (PixelType typeInFrameBuffer = HALF, PixelType typeInFile = HALF, char *base = 0, size_t xStride = 0, size_t yStride = 0, int xSampling = 1, int ySampling = 1, bool fill = false, bool skip = false, double fillValue = 0.0); }; InSliceInfo::InSliceInfo (PixelType tifb, PixelType tifl, char *b, size_t xs, size_t ys, int xsm, int ysm, bool f, bool s, double fv) : typeInFrameBuffer (tifb), typeInFile (tifl), base (b), xStride (xs), yStride (ys), xSampling (xsm), ySampling (ysm), fill (f), skip (s), fillValue (fv) { // empty } } // namespace struct InputFile::Data { public: string fileName; Header header; int version; FrameBuffer frameBuffer; LineOrder lineOrder; int minX; int maxX; int minY; int maxY; vector lineOffsets; int linesInBuffer; int lineBufferMinY; int lineBufferMaxY; int nextLineBufferMinY; size_t lineBufferSize; Array lineBuffer; const char * uncompressedData; vector bytesPerLine; vector offsetInLineBuffer; Compressor * compressor; Compressor::Format format; vector slices; ifstream is; Data (): compressor (0) {} ~Data () {delete compressor;} }; namespace { void reconstructLineOffsets (ifstream &is, vector &lineOffsets) { long position = is.tellg(); try { for (unsigned int i = 0; i < lineOffsets.size(); i++) { if (!is) break; long lineOffset = is.tellg(); int y; Xdr::read (is, y); if (!is) break; int dataSize; Xdr::read (is, dataSize); if (!is) break; Xdr::skip (is, dataSize); lineOffsets[i] = lineOffset; } } catch (...) { // // Suppress all exceptions. This functions is // called only to reconstruct the line offset // table for incomplete files, and exceptions // are likely. // } is.clear(); is.seekg (position); } void readLineOffsets (ifstream &is, vector &lineOffsets) { for (unsigned int i = 0; i < lineOffsets.size(); i++) { Xdr::read (is, lineOffsets[i]); } for (unsigned int i = 0; i < lineOffsets.size(); i++) { if (lineOffsets[i] <= 0) { // // Invalid data in the line offset table mean that // the file is probably incomplete (the table is // the last thing written to the file). Either // some process is still busy writing the file, // or writing the file was aborted. // // We should still be able to read the existing // parts of the file. In order to do this, we // have to make a sequential scan over the scan // line data to reconstruct the line offset table. // reconstructLineOffsets (is, lineOffsets); break; } } } void readPixelData (InputFile::Data *ifd, int y, int &minY, int &maxY, int &dataSize) { long lineOffset = ifd->lineOffsets[(y - ifd->minY) / ifd->linesInBuffer]; if (lineOffset == 0) THROW (Iex::InputExc, "Scan line " << y << " is missing."); // // Seek to the start of the scan line in the file, // if necessary. // minY = lineBufferMinY (y, ifd->minY, ifd->linesInBuffer); maxY = lineBufferMaxY (y, ifd->minY, ifd->linesInBuffer); if (ifd->nextLineBufferMinY != minY) { ifd->is.seekg (lineOffset); checkError (ifd->is); } #ifdef DEBUG assert (long (ifd->is.tellg()) == lineOffset); #endif // // Read the data block's header. // int yInFile; Xdr::read (ifd->is, yInFile); Xdr::read (ifd->is, dataSize); if (yInFile != minY) throw Iex::InputExc ("Unexpected data block y coordinate."); if (dataSize > (int) ifd->lineBufferSize) throw Iex::InputExc ("Unexpected data block length."); // // Read the pixel data. // ifd->is.read (ifd->lineBuffer, dataSize); checkError (ifd->is); // // Keep track of which scan line is the next one in // the file, so that we can avoid redundant seekg() // operations (seekg() can be fairly expensive). // if (ifd->lineOrder == INCREASING_Y) ifd->nextLineBufferMinY = minY + ifd->linesInBuffer; else ifd->nextLineBufferMinY = minY - ifd->linesInBuffer; } } // namespace InputFile::InputFile (const char fileName[]): _data (new Data) { try { _data->fileName = fileName; #ifndef HAVE_IOS_BASE _data->is.open (fileName, std::ios::binary|std::ios::in); #else _data->is.open (fileName, std::ios_base::binary); #endif if (!_data->is) Iex::throwErrnoExc(); _data->header.readFrom (_data->is, _data->version); _data->header.sanityCheck(); _data->lineOrder = _data->header.lineOrder(); const Box2i &dataWindow = _data->header.dataWindow(); _data->minX = dataWindow.min.x; _data->maxX = dataWindow.max.x; _data->minY = dataWindow.min.y; _data->maxY = dataWindow.max.y; _data->lineOffsets.resize (dataWindow.max.y - dataWindow.min.y + 1); int maxBytesPerLine = bytesPerLineTable (_data->header, _data->bytesPerLine); _data->compressor = newCompressor (_data->header.compression(), maxBytesPerLine, _data->header); _data->linesInBuffer = _data->compressor? _data->compressor->numScanLines(): 1; _data->format = _data->compressor? _data->compressor->format(): Compressor::XDR; _data->lineBufferSize = maxBytesPerLine * _data->linesInBuffer; _data->lineBuffer.resizeErase (_data->lineBufferSize); _data->lineBufferMinY = _data->minY - 1; _data->lineBufferMaxY = _data->minY - 1; _data->nextLineBufferMinY = _data->minY - 1; _data->uncompressedData = 0; offsetInLineBufferTable (_data->bytesPerLine, _data->linesInBuffer, _data->offsetInLineBuffer); int lineOffsetSize = (dataWindow.max.y - dataWindow.min.y + _data->linesInBuffer) / _data->linesInBuffer; _data->lineOffsets.resize (lineOffsetSize); readLineOffsets (_data->is, _data->lineOffsets); } catch (Iex::BaseExc &e) { delete _data; REPLACE_EXC (e, "Cannot read image file \"" << fileName << "\". " << e); throw; } } InputFile::~InputFile () { delete _data; } const char * InputFile::fileName () const { return _data->fileName.c_str(); } const Header & InputFile::header () const { return _data->header; } int InputFile::version () const { return _data->version; } void InputFile::setFrameBuffer (const FrameBuffer &frameBuffer) { // // Check if the new frame buffer descriptor is // compatible with the image file header. // const ChannelList &channels = _data->header.channels(); for (FrameBuffer::ConstIterator j = frameBuffer.begin(); j != frameBuffer.end(); ++j) { ChannelList::ConstIterator i = channels.find (j.name()); if (i == channels.end()) continue; if (i.channel().xSampling != j.slice().xSampling || i.channel().ySampling != j.slice().ySampling) { THROW (Iex::ArgExc, "X and/or y subsampling factors " "of \"" << i.name() << "\" channel " "of input file \"" << fileName() << "\" are " "not compatible with the frame buffer's " "subsampling factors."); } } // // Initialize the slice table for readPixels(). // vector slices; ChannelList::ConstIterator i = channels.begin(); for (FrameBuffer::ConstIterator j = frameBuffer.begin(); j != frameBuffer.end(); ++j) { while (i != channels.end() && strcmp (i.name(), j.name()) < 0) { // // Channel i is present in the file but not // in the frame buffer; data for channel i // will be skipped during readPixels(). // slices.push_back (InSliceInfo (i.channel().type, i.channel().type, 0, // base 0, // xStride 0, // yStride i.channel().xSampling, i.channel().ySampling, false, // fill true, // skip 0.0)); // fillValue ++i; } bool fill = false; if (i == channels.end() || strcmp (i.name(), j.name()) > 0) { // // Channel i is present in the frame buffer, but not in the file. // In the frame buffer, slice j will be filled with a default value. // fill = true; } slices.push_back (InSliceInfo (j.slice().type, fill ? j.slice().type: i.channel().type, j.slice().base, j.slice().xStride, j.slice().yStride, j.slice().xSampling, j.slice().ySampling, fill, false, // skip j.slice().fillValue)); if (i != channels.end() && !fill) ++i; } // // Store the new frame buffer. // _data->frameBuffer = frameBuffer; _data->slices = slices; } const FrameBuffer & InputFile::frameBuffer () const { return _data->frameBuffer; } void InputFile::readPixels (int scanLine1, int scanLine2) { try { if (_data->slices.size() == 0) throw Iex::ArgExc ("No frame buffer specified " "as pixel data source."); // // Try to read the scan lines in the same order as in // the file to reduce the overhead of seek operations. // #if defined PLATFORM_WIN32 && _MSC_VER < 1300 int minY = min (scanLine1, scanLine2); int maxY = max (scanLine1, scanLine2); #else int minY = std::min (scanLine1, scanLine2); int maxY = std::max (scanLine1, scanLine2); #endif if (minY < _data->minY || maxY > _data->maxY) { throw Iex::ArgExc ("Tried to read scan line outside " "the image file's data window."); } int numScanLines = maxY - minY + 1; int y; int dy; if (_data->lineOrder == INCREASING_Y) { y = minY; dy = 1; } else { y = maxY; dy = -1; } bool forceXdr = false; // Used to force the lineBuffer to be // interpreted as Xdr. This is needed // if the compressor output pixel data // in the machine's native format, but // lineBuffer contains uncompressed // data in Xdr format. (In a compressed // image file, pixel data that cannot // be compressed because they are too // random, are stored in uncompressed // form.) while (numScanLines) { // // If necessary, read the data block, that // contains line y, from the output file. // if (y < _data->lineBufferMinY || y > _data->lineBufferMaxY) { int minY, maxY, dataSize; readPixelData (_data, y, minY, maxY, dataSize); forceXdr = false; // // Uncompress the data, if necessary // int uncompressedSize = 0; int max = std::min (maxY, _data->maxY); for (int i = minY - _data->minY; i <= max - _data->minY; ++i) uncompressedSize += (int) _data->bytesPerLine[i]; if (_data->compressor && dataSize < uncompressedSize) { dataSize = _data->compressor->uncompress (_data->lineBuffer, dataSize, minY, _data->uncompressedData); } else { // // If the line is uncompressed, but the compressor // says that it's in native format, don't believe it. // if (_data->format != Compressor::XDR) forceXdr = true; _data->uncompressedData = _data->lineBuffer; } _data->lineBufferMinY = minY; _data->lineBufferMaxY = maxY; } // // Convert one scan line's worth of pixel data back // from the machine-independent representation, and // store the result in the frame buffer. // const char *readPtr = _data->uncompressedData + _data->offsetInLineBuffer[y - _data->minY]; // // Iterate over all image channels. // for (unsigned int i = 0; i < _data->slices.size(); ++i) { // // Test if scan line y of this channel is // contains any data (the scan line contains // data only if y % ySampling == 0). // const InSliceInfo &slice = _data->slices[i]; if (modp (y, slice.ySampling) != 0) continue; // // Find the x coordinates of the leftmost and rightmost // sampled pixels (i.e. pixels within the data window // for which x % xSampling == 0). // int dMinX = divp (_data->minX, slice.xSampling); int dMaxX = divp (_data->maxX, slice.xSampling); // // Iterate over the sampled pixels. // if (slice.skip) { // // The file contains data for this channel, but // the frame buffer contains no slice for this channel. // switch (slice.typeInFile) { case UINT: Xdr::skip (readPtr, Xdr::size () * (dMaxX - dMinX + 1)); break; case HALF: Xdr::skip (readPtr, Xdr::size () * (dMaxX - dMinX + 1)); break; case FLOAT: Xdr::skip (readPtr, Xdr::size () * (dMaxX - dMinX + 1)); break; default: throw Iex::ArgExc ("Unknown pixel data type."); } } else { // // The frame buffer contains a slice for this channel. // char *linePtr = slice.base + divp (y, slice.ySampling) * slice.yStride; char *pixelPtr = linePtr + dMinX * slice.xStride; char *endPtr = linePtr + dMaxX * slice.xStride; if (slice.fill) { // // The file contains no data for this channel. // Store a default value in the frame buffer. // switch (slice.typeInFrameBuffer) { case UINT: { unsigned int fillValue = (unsigned int) (slice.fillValue); while (pixelPtr <= endPtr) { *(unsigned int *) pixelPtr = fillValue; pixelPtr += slice.xStride; } } break; case HALF: { half fillValue = half (slice.fillValue); while (pixelPtr <= endPtr) { *(half *) pixelPtr = fillValue; pixelPtr += slice.xStride; } } break; case FLOAT: { float fillValue = float (slice.fillValue); while (pixelPtr <= endPtr) { *(float *) pixelPtr = fillValue; pixelPtr += slice.xStride; } } break; default: throw Iex::ArgExc ("Unknown pixel data type."); } } else if (_data->format == Compressor::XDR || forceXdr) { // // The compressor produced data for this // channel in Xdr format. // // Convert the pixels from the file's machine- // independent representation, and store the // results in the frame buffer. // switch (slice.typeInFrameBuffer) { case UINT: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { Xdr::read (readPtr, *(unsigned int *) pixelPtr); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { half h; Xdr::read (readPtr, h); *(unsigned int *) pixelPtr = halfToUint (h); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { float f; Xdr::read (readPtr, f); *(unsigned int *)pixelPtr = floatToUint (f); pixelPtr += slice.xStride; } break; } break; case HALF: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { unsigned int ui; Xdr::read (readPtr, ui); *(half *) pixelPtr = uintToHalf (ui); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { Xdr::read (readPtr, *(half *) pixelPtr); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { float f; Xdr::read (readPtr, f); *(half *) pixelPtr = floatToHalf (f); pixelPtr += slice.xStride; } break; } break; case FLOAT: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { unsigned int ui; Xdr::read (readPtr, ui); *(float *) pixelPtr = float (ui); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { half h; Xdr::read (readPtr, h); *(float *) pixelPtr = float (h); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { Xdr::read (readPtr, *(float *) pixelPtr); pixelPtr += slice.xStride; } break; } break; default: throw Iex::ArgExc ("Unknown pixel data type."); } } else { // // The compressor produced data for this // channel in the machine's native format. // Copy the results into the frame buffer. // switch (slice.typeInFrameBuffer) { case UINT: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { for (size_t i = 0; i < sizeof (unsigned int); ++i) { pixelPtr[i] = readPtr[i]; } readPtr += sizeof (unsigned int); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { half h = *(half *) readPtr; *(unsigned int *) pixelPtr = halfToUint (h); readPtr += sizeof (half); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { float f; for (size_t i = 0; i < sizeof (float); ++i) { ((char *)&f)[i] = readPtr[i]; } *(unsigned int *)pixelPtr = floatToUint (f); readPtr += sizeof (float); pixelPtr += slice.xStride; } break; } break; case HALF: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { unsigned int ui; for (size_t i = 0; i < sizeof (unsigned int); ++i) { ((char *)&ui)[i] = readPtr[i]; } *(half *) pixelPtr = uintToHalf (ui); readPtr += sizeof (unsigned int); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { *(half *) pixelPtr = *(half *)readPtr; readPtr += sizeof (half); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { float f; for (size_t i = 0; i < sizeof (float); ++i) { ((char *)&f)[i] = readPtr[i]; } *(half *) pixelPtr = floatToHalf (f); readPtr += sizeof (float); pixelPtr += slice.xStride; } break; } break; case FLOAT: switch (slice.typeInFile) { case UINT: while (pixelPtr <= endPtr) { unsigned int ui; for (size_t i = 0; i < sizeof (unsigned int); ++i) { ((char *)&ui)[i] = readPtr[i]; } *(float *) pixelPtr = float (ui); readPtr += sizeof (unsigned int); pixelPtr += slice.xStride; } break; case HALF: while (pixelPtr <= endPtr) { half h = *(half *) readPtr; *(float *) pixelPtr = float (h); readPtr += sizeof (half); pixelPtr += slice.xStride; } break; case FLOAT: while (pixelPtr <= endPtr) { for (size_t i = 0; i < sizeof (float); ++i) pixelPtr[i] = readPtr[i]; readPtr += sizeof (float); pixelPtr += slice.xStride; } break; } break; default: throw Iex::ArgExc ("Unknown pixel data type."); } } } } // // Advance to the next scan line. // numScanLines -= 1; y += dy; } } catch (Iex::BaseExc &e) { REPLACE_EXC (e, "Error reading pixel data from image " "file \"" << fileName() << "\". " << e); throw; } } void InputFile::readPixels (int scanLine) { readPixels (scanLine, scanLine); } void InputFile::rawPixelData (int firstScanLine, const char *&pixelData, int &pixelDataSize) { try { if (firstScanLine < _data->minY || firstScanLine > _data->maxY) { throw Iex::ArgExc ("Tried to read scan line outside " "the image file's data window."); } int dummy1, dummy2; readPixelData (_data, firstScanLine, dummy1, dummy2, pixelDataSize); pixelData = _data->lineBuffer; } catch (Iex::BaseExc &e) { REPLACE_EXC (e, "Error reading pixel data from image " "file \"" << fileName() << "\". " << e); throw; } } } // namespace Imf