diff options
Diffstat (limited to 'Source/OpenEXR/IlmImf/ImfTiledOutputFile.cpp')
-rw-r--r-- | Source/OpenEXR/IlmImf/ImfTiledOutputFile.cpp | 1692 |
1 files changed, 1692 insertions, 0 deletions
diff --git a/Source/OpenEXR/IlmImf/ImfTiledOutputFile.cpp b/Source/OpenEXR/IlmImf/ImfTiledOutputFile.cpp new file mode 100644 index 0000000..9a96ac9 --- /dev/null +++ b/Source/OpenEXR/IlmImf/ImfTiledOutputFile.cpp @@ -0,0 +1,1692 @@ +/////////////////////////////////////////////////////////////////////////// +// +// 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 TiledOutputFile +// +//----------------------------------------------------------------------------- + +#include <ImfTiledOutputFile.h> +#include <ImfTiledInputFile.h> +#include <ImfInputFile.h> +#include <ImfTileDescriptionAttribute.h> +#include <ImfPreviewImageAttribute.h> +#include <ImfChannelList.h> +#include <ImfMisc.h> +#include <ImfTiledMisc.h> +#include <ImfStdIO.h> +#include <ImfCompressor.h> +#include "ImathBox.h" +#include <ImfArray.h> +#include <ImfXdr.h> +#include <ImfVersion.h> +#include <ImfTileOffsets.h> +#include <ImfThreading.h> +#include "IlmThreadPool.h" +#include "IlmThreadSemaphore.h" +#include "IlmThreadMutex.h" +#include "Iex.h" +#include <string> +#include <vector> +#include <fstream> +#include <assert.h> +#include <map> + + +namespace Imf { + +using Imath::Box2i; +using Imath::V2i; +using std::string; +using std::vector; +using std::ofstream; +using std::map; +using std::min; +using std::max; +using std::swap; +using IlmThread::Mutex; +using IlmThread::Lock; +using IlmThread::Semaphore; +using IlmThread::Task; +using IlmThread::TaskGroup; +using IlmThread::ThreadPool; + +namespace { + +struct TOutSliceInfo +{ + PixelType type; + const char * base; + size_t xStride; + size_t yStride; + bool zero; + int xTileCoords; + int yTileCoords; + + TOutSliceInfo (PixelType type = HALF, + const char *base = 0, + size_t xStride = 0, + size_t yStride = 0, + bool zero = false, + int xTileCoords = 0, + int yTileCoords = 0); +}; + + +TOutSliceInfo::TOutSliceInfo (PixelType t, + const char *b, + size_t xs, size_t ys, + bool z, + int xtc, + int ytc) +: + type (t), + base (b), + xStride (xs), + yStride (ys), + zero (z), + xTileCoords (xtc), + yTileCoords (ytc) +{ + // empty +} + + +struct TileCoord +{ + int dx; + int dy; + int lx; + int ly; + + + TileCoord (int xTile = 0, int yTile = 0, + int xLevel = 0, int yLevel = 0) + : + dx (xTile), dy (yTile), + lx (xLevel), ly (yLevel) + { + // empty + } + + + bool + operator < (const TileCoord &other) const + { + return (ly < other.ly) || + (ly == other.ly && lx < other.lx) || + ((ly == other.ly && lx == other.lx) && + ((dy < other.dy) || (dy == other.dy && dx < other.dx))); + } + + + bool + operator == (const TileCoord &other) const + { + return lx == other.lx && + ly == other.ly && + dx == other.dx && + dy == other.dy; + } +}; + + +struct BufferedTile +{ + char * pixelData; + int pixelDataSize; + + BufferedTile (const char *data, int size): + pixelData (0), + pixelDataSize(size) + { + pixelData = new char[pixelDataSize]; + memcpy (pixelData, data, pixelDataSize); + } + + ~BufferedTile() + { + delete [] pixelData; + } +}; + + +typedef map <TileCoord, BufferedTile *> TileMap; + + +struct TileBuffer +{ + Array<char> buffer; + const char * dataPtr; + int dataSize; + Compressor * compressor; + TileCoord tileCoord; + bool hasException; + string exception; + + TileBuffer (Compressor *comp); + ~TileBuffer (); + + inline void wait () {_sem.wait();} + inline void post () {_sem.post();} + + protected: + + Semaphore _sem; +}; + + +TileBuffer::TileBuffer (Compressor *comp): + dataPtr (0), + dataSize (0), + compressor (comp), + hasException (false), + exception (), + _sem (1) +{ + // empty +} + + +TileBuffer::~TileBuffer () +{ + delete compressor; +} + + +} // namespace + + +struct TiledOutputFile::Data: public Mutex +{ + Header header; // the image header + int version; // file format version + TileDescription tileDesc; // describes the tile layout + FrameBuffer frameBuffer; // framebuffer to write into + Int64 previewPosition; + LineOrder lineOrder; // the file's lineorder + int minX; // data window's min x coord + int maxX; // data window's max x coord + int minY; // data window's min y coord + int maxY; // data window's max x coord + + int numXLevels; // number of x levels + int numYLevels; // number of y levels + int * numXTiles; // number of x tiles at a level + int * numYTiles; // number of y tiles at a level + + TileOffsets tileOffsets; // stores offsets in file for + // each tile + + Compressor::Format format; // compressor's data format + vector<TOutSliceInfo> slices; // info about channels in file + OStream * os; // file stream to write to + bool deleteStream; + + size_t maxBytesPerTileLine; // combined size of a tile line + // over all channels + + + vector<TileBuffer*> tileBuffers; + size_t tileBufferSize; // size of a tile buffer + + Int64 tileOffsetsPosition; // position of the tile index + Int64 currentPosition; // current position in the file + + TileMap tileMap; + TileCoord nextTileToWrite; + + Data (bool del, int numThreads); + ~Data (); + + inline TileBuffer * getTileBuffer (int number); + // hash function from tile + // buffer coords into our + // vector of tile buffers + + TileCoord nextTileCoord (const TileCoord &a); +}; + + +TiledOutputFile::Data::Data (bool del, int numThreads): + numXTiles(0), + numYTiles(0), + os (0), + deleteStream (del), + tileOffsetsPosition (0) +{ + // + // We need at least one tileBuffer, but if threading is used, + // to keep n threads busy we need 2*n tileBuffers + // + + tileBuffers.resize (max (1, 2 * numThreads)); +} + + +TiledOutputFile::Data::~Data () +{ + delete [] numXTiles; + delete [] numYTiles; + + if (deleteStream) + delete os; + + // + // Delete all the tile buffers, if any still happen to exist + // + + for (TileMap::iterator i = tileMap.begin(); i != tileMap.end(); ++i) + delete i->second; + + for (size_t i = 0; i < tileBuffers.size(); i++) + delete tileBuffers[i]; +} + + +TileBuffer* +TiledOutputFile::Data::getTileBuffer (int number) +{ + return tileBuffers[number % tileBuffers.size()]; +} + + +TileCoord +TiledOutputFile::Data::nextTileCoord (const TileCoord &a) +{ + TileCoord b = a; + + if (lineOrder == INCREASING_Y) + { + b.dx++; + + if (b.dx >= numXTiles[b.lx]) + { + b.dx = 0; + b.dy++; + + if (b.dy >= numYTiles[b.ly]) + { + // + // the next tile is in the next level + // + + b.dy = 0; + + switch (tileDesc.mode) + { + case ONE_LEVEL: + case MIPMAP_LEVELS: + + b.lx++; + b.ly++; + break; + + case RIPMAP_LEVELS: + + b.lx++; + + if (b.lx >= numXLevels) + { + b.lx = 0; + b.ly++; + + #ifdef DEBUG + assert (b.ly <= numYLevels); + #endif + } + break; + } + } + } + } + else if (lineOrder == DECREASING_Y) + { + b.dx++; + + if (b.dx >= numXTiles[b.lx]) + { + b.dx = 0; + b.dy--; + + if (b.dy < 0) + { + // + // the next tile is in the next level + // + + switch (tileDesc.mode) + { + case ONE_LEVEL: + case MIPMAP_LEVELS: + + b.lx++; + b.ly++; + break; + + case RIPMAP_LEVELS: + + b.lx++; + + if (b.lx >= numXLevels) + { + b.lx = 0; + b.ly++; + + #ifdef DEBUG + assert (b.ly <= numYLevels); + #endif + } + break; + } + + if (b.ly < numYLevels) + b.dy = numYTiles[b.ly] - 1; + } + } + } + + return b; +} + + +namespace { + +void +writeTileData (TiledOutputFile::Data *ofd, + int dx, int dy, + int lx, int ly, + const char pixelData[], + int pixelDataSize) +{ + // + // Store a block of pixel data in the output file, and try + // to keep track of the current writing position the file, + // without calling tellp() (tellp() can be fairly expensive). + // + + Int64 currentPosition = ofd->currentPosition; + ofd->currentPosition = 0; + + if (currentPosition == 0) + currentPosition = ofd->os->tellp(); + + ofd->tileOffsets (dx, dy, lx, ly) = currentPosition; + + #ifdef DEBUG + assert (ofd->os->tellp() == currentPosition); + #endif + + // + // Write the tile header. + // + + Xdr::write <StreamIO> (*ofd->os, dx); + Xdr::write <StreamIO> (*ofd->os, dy); + Xdr::write <StreamIO> (*ofd->os, lx); + Xdr::write <StreamIO> (*ofd->os, ly); + Xdr::write <StreamIO> (*ofd->os, pixelDataSize); + + ofd->os->write (pixelData, pixelDataSize); + + // + // Keep current position in the file so that we can avoid + // redundant seekg() operations (seekg() can be fairly expensive). + // + + ofd->currentPosition = currentPosition + + 5 * Xdr::size<int>() + + pixelDataSize; +} + + + +void +bufferedTileWrite (TiledOutputFile::Data *ofd, + int dx, int dy, + int lx, int ly, + const char pixelData[], + int pixelDataSize) +{ + // + // Check if a tile with coordinates (dx,dy,lx,ly) has already been written. + // + + if (ofd->tileOffsets (dx, dy, lx, ly)) + { + THROW (Iex::ArgExc, + "Attempt to write tile " + "(" << dx << ", " << dy << ", " << lx << "," << ly << ") " + "more than once."); + } + + // + // If tiles can be written in random order, then don't buffer anything. + // + + if (ofd->lineOrder == RANDOM_Y) + { + writeTileData (ofd, dx, dy, lx, ly, pixelData, pixelDataSize); + return; + } + + // + // If the tiles cannot be written in random order, then check if a + // tile with coordinates (dx,dy,lx,ly) has already been buffered. + // + + TileCoord currentTile = TileCoord(dx, dy, lx, ly); + + if (ofd->tileMap.find (currentTile) != ofd->tileMap.end()) + { + THROW (Iex::ArgExc, + "Attempt to write tile " + "(" << dx << ", " << dy << ", " << lx << "," << ly << ") " + "more than once."); + } + + // + // If all the tiles before this one have already been written to the file, + // then write this tile immediately and check if we have buffered tiles + // that can be written after this tile. + // + // Otherwise, buffer the tile so it can be written to file later. + // + + if (ofd->nextTileToWrite == currentTile) + { + writeTileData (ofd, dx, dy, lx, ly, pixelData, pixelDataSize); + ofd->nextTileToWrite = ofd->nextTileCoord (ofd->nextTileToWrite); + + TileMap::iterator i = ofd->tileMap.find (ofd->nextTileToWrite); + + // + // Step through the tiles and write all successive buffered tiles after + // the current one. + // + + while(i != ofd->tileMap.end()) + { + // + // Write the tile, and then delete the tile's buffered data + // + + writeTileData (ofd, + i->first.dx, i->first.dy, + i->first.lx, i->first.ly, + i->second->pixelData, + i->second->pixelDataSize); + + delete i->second; + ofd->tileMap.erase (i); + + // + // Proceed to the next tile + // + + ofd->nextTileToWrite = ofd->nextTileCoord (ofd->nextTileToWrite); + i = ofd->tileMap.find (ofd->nextTileToWrite); + } + } + else + { + // + // Create a new BufferedTile, copy the pixelData into it, and + // insert it into the tileMap. + // + + ofd->tileMap[currentTile] = + new BufferedTile ((const char *)pixelData, pixelDataSize); + } +} + + +void +convertToXdr (TiledOutputFile::Data *ofd, + Array<char>& tileBuffer, + int numScanLines, + int numPixelsPerScanLine) +{ + // + // Convert the contents of a TiledOutputFile's tileBuffer from the + // machine's native representation to Xdr format. This function is called + // by writeTile(), below, if the compressor wanted its input pixel data + // in the machine's native format, but then failed to compress the data + // (most compressors will expand rather than compress random input data). + // + // Note that this routine assumes that the machine's native representation + // of the pixel data has the same size as the Xdr representation. This + // makes it possible to convert the pixel data in place, without an + // intermediate temporary buffer. + // + + // + // Set these to point to the start of the tile. + // We will write to toPtr, and read from fromPtr. + // + + char *writePtr = tileBuffer; + const char *readPtr = writePtr; + + // + // Iterate over all scan lines in the tile. + // + + for (int y = 0; y < numScanLines; ++y) + { + // + // Iterate over all slices in the file. + // + + for (unsigned int i = 0; i < ofd->slices.size(); ++i) + { + const TOutSliceInfo &slice = ofd->slices[i]; + + // + // Convert the samples in place. + // + + convertInPlace (writePtr, readPtr, slice.type, + numPixelsPerScanLine); + } + } + + #ifdef DEBUG + + assert (writePtr == readPtr); + + #endif +} + + +// +// A TileBufferTask encapsulates the task of copying a tile from +// the user's framebuffer into a LineBuffer and compressing the data +// if necessary. +// + +class TileBufferTask: public Task +{ + public: + + TileBufferTask (TaskGroup *group, + TiledOutputFile::Data *ofd, + int number, + int dx, int dy, + int lx, int ly); + + virtual ~TileBufferTask (); + + virtual void execute (); + + private: + + TiledOutputFile::Data * _ofd; + TileBuffer * _tileBuffer; +}; + + +TileBufferTask::TileBufferTask + (TaskGroup *group, + TiledOutputFile::Data *ofd, + int number, + int dx, int dy, + int lx, int ly) +: + Task (group), + _ofd (ofd), + _tileBuffer (_ofd->getTileBuffer (number)) +{ + // + // Wait for the tileBuffer to become available + // + + _tileBuffer->wait (); + _tileBuffer->tileCoord = TileCoord (dx, dy, lx, ly); +} + + +TileBufferTask::~TileBufferTask () +{ + // + // Signal that the tile buffer is now free + // + + _tileBuffer->post (); +} + + +void +TileBufferTask::execute () +{ + try + { + // + // First copy the pixel data from the frame buffer + // into the tile buffer + // + // Convert one tile's worth of pixel data to + // a machine-independent representation, and store + // the result in _tileBuffer->buffer. + // + + char *writePtr = _tileBuffer->buffer; + + Box2i tileRange = Imf::dataWindowForTile (_ofd->tileDesc, + _ofd->minX, _ofd->maxX, + _ofd->minY, _ofd->maxY, + _tileBuffer->tileCoord.dx, + _tileBuffer->tileCoord.dy, + _tileBuffer->tileCoord.lx, + _tileBuffer->tileCoord.ly); + + int numScanLines = tileRange.max.y - tileRange.min.y + 1; + int numPixelsPerScanLine = tileRange.max.x - tileRange.min.x + 1; + + // + // Iterate over the scan lines in the tile. + // + + for (int y = tileRange.min.y; y <= tileRange.max.y; ++y) + { + // + // Iterate over all image channels. + // + + for (unsigned int i = 0; i < _ofd->slices.size(); ++i) + { + const TOutSliceInfo &slice = _ofd->slices[i]; + + // + // These offsets are used to facilitate both absolute + // and tile-relative pixel coordinates. + // + + int xOffset = slice.xTileCoords * tileRange.min.x; + int yOffset = slice.yTileCoords * tileRange.min.y; + + // + // Fill the tile buffer with pixel data. + // + + if (slice.zero) + { + // + // The frame buffer contains no data for this channel. + // Store zeroes in _data->tileBuffer. + // + + fillChannelWithZeroes (writePtr, _ofd->format, slice.type, + numPixelsPerScanLine); + } + else + { + // + // The frame buffer contains data for this channel. + // + + const char *readPtr = slice.base + + (y - yOffset) * slice.yStride + + (tileRange.min.x - xOffset) * + slice.xStride; + + const char *endPtr = readPtr + + (numPixelsPerScanLine - 1) * + slice.xStride; + + copyFromFrameBuffer (writePtr, readPtr, endPtr, + slice.xStride, _ofd->format, + slice.type); + } + } + } + + // + // Compress the contents of the tileBuffer, + // and store the compressed data in the output file. + // + + _tileBuffer->dataSize = writePtr - _tileBuffer->buffer; + _tileBuffer->dataPtr = _tileBuffer->buffer; + + if (_tileBuffer->compressor) + { + const char *compPtr; + + int compSize = _tileBuffer->compressor->compressTile + (_tileBuffer->dataPtr, + _tileBuffer->dataSize, + tileRange, compPtr); + + if (compSize < _tileBuffer->dataSize) + { + _tileBuffer->dataSize = compSize; + _tileBuffer->dataPtr = compPtr; + } + else if (_ofd->format == Compressor::NATIVE) + { + // + // The data did not shrink during compression, but + // we cannot write to the file using native format, + // so we need to convert the lineBuffer to Xdr. + // + + convertToXdr (_ofd, _tileBuffer->buffer, numScanLines, + numPixelsPerScanLine); + } + } + } + catch (std::exception &e) + { + if (!_tileBuffer->hasException) + { + _tileBuffer->exception = e.what (); + _tileBuffer->hasException = true; + } + } + catch (...) + { + if (!_tileBuffer->hasException) + { + _tileBuffer->exception = "unrecognized exception"; + _tileBuffer->hasException = true; + } + } +} + +} // namespace + + +TiledOutputFile::TiledOutputFile + (const char fileName[], + const Header &header, + int numThreads) +: + _data (new Data (true, numThreads)) +{ + try + { + header.sanityCheck (true); + _data->os = new StdOFStream (fileName); + initialize (header); + } + catch (Iex::BaseExc &e) + { + delete _data; + + REPLACE_EXC (e, "Cannot open image file " + "\"" << fileName << "\". " << e); + throw; + } + catch (...) + { + delete _data; + throw; + } +} + + +TiledOutputFile::TiledOutputFile + (OStream &os, + const Header &header, + int numThreads) +: + _data (new Data (false, numThreads)) +{ + try + { + header.sanityCheck(true); + _data->os = &os; + initialize (header); + } + catch (Iex::BaseExc &e) + { + delete _data; + + REPLACE_EXC (e, "Cannot open image file " + "\"" << os.fileName() << "\". " << e); + throw; + } + catch (...) + { + delete _data; + throw; + } +} + + +void +TiledOutputFile::initialize (const Header &header) +{ + _data->header = header; + _data->lineOrder = _data->header.lineOrder(); + + // + // Check that the file is indeed tiled + // + + _data->tileDesc = _data->header.tileDescription(); + + // + // Save the dataWindow information + // + + 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; + + // + // Precompute level and tile information to speed up utility functions + // + + precalculateTileInfo (_data->tileDesc, + _data->minX, _data->maxX, + _data->minY, _data->maxY, + _data->numXTiles, _data->numYTiles, + _data->numXLevels, _data->numYLevels); + + // + // Determine the first tile coordinate that we will be writing + // if the file is not RANDOM_Y. + // + + _data->nextTileToWrite = (_data->lineOrder == INCREASING_Y)? + TileCoord (0, 0, 0, 0): + TileCoord (0, _data->numYTiles[0] - 1, 0, 0); + + _data->maxBytesPerTileLine = + calculateBytesPerPixel (_data->header) * _data->tileDesc.xSize; + + _data->tileBufferSize = _data->maxBytesPerTileLine * _data->tileDesc.ySize; + + // + // Create all the TileBuffers and allocate their internal buffers + // + + for (size_t i = 0; i < _data->tileBuffers.size(); i++) + { + _data->tileBuffers[i] = new TileBuffer (newTileCompressor + (_data->header.compression(), + _data->maxBytesPerTileLine, + _data->tileDesc.ySize, + _data->header)); + + _data->tileBuffers[i]->buffer.resizeErase(_data->tileBufferSize); + } + + _data->format = defaultFormat (_data->tileBuffers[0]->compressor); + + _data->tileOffsets = TileOffsets (_data->tileDesc.mode, + _data->numXLevels, + _data->numYLevels, + _data->numXTiles, + _data->numYTiles); + + _data->previewPosition = _data->header.writeTo (*_data->os, true); + + _data->tileOffsetsPosition = _data->tileOffsets.writeTo (*_data->os); + _data->currentPosition = _data->os->tellp(); +} + + +TiledOutputFile::~TiledOutputFile () +{ + if (_data) + { + { + if (_data->tileOffsetsPosition > 0) + { + try + { + _data->os->seekp (_data->tileOffsetsPosition); + _data->tileOffsets.writeTo (*_data->os); + } + catch (...) + { + // + // We cannot safely throw any exceptions from here. + // This destructor may have been called because the + // stack is currently being unwound for another + // exception. + // + } + } + } + + delete _data; + } +} + + +const char * +TiledOutputFile::fileName () const +{ + return _data->os->fileName(); +} + + +const Header & +TiledOutputFile::header () const +{ + return _data->header; +} + + +void +TiledOutputFile::setFrameBuffer (const FrameBuffer &frameBuffer) +{ + Lock lock (*_data); + + // + // Check if the new frame buffer descriptor + // is compatible with the image file header. + // + + const ChannelList &channels = _data->header.channels(); + + for (ChannelList::ConstIterator i = channels.begin(); + i != channels.end(); + ++i) + { + FrameBuffer::ConstIterator j = frameBuffer.find (i.name()); + + if (j == frameBuffer.end()) + continue; + + if (i.channel().type != j.slice().type) + THROW (Iex::ArgExc, "Pixel type of \"" << i.name() << "\" channel " + "of output file \"" << fileName() << "\" is " + "not compatible with the frame buffer's " + "pixel type."); + + if (j.slice().xSampling != 1 || j.slice().ySampling != 1) + THROW (Iex::ArgExc, "All channels in a tiled file must have" + "sampling (1,1)."); + } + + // + // Initialize slice table for writePixels(). + // + + vector<TOutSliceInfo> slices; + + for (ChannelList::ConstIterator i = channels.begin(); + i != channels.end(); + ++i) + { + FrameBuffer::ConstIterator j = frameBuffer.find (i.name()); + + if (j == frameBuffer.end()) + { + // + // Channel i is not present in the frame buffer. + // In the file, channel i will contain only zeroes. + // + + slices.push_back (TOutSliceInfo (i.channel().type, + 0, // base + 0, // xStride, + 0, // yStride, + true)); // zero + } + else + { + // + // Channel i is present in the frame buffer. + // + + slices.push_back (TOutSliceInfo (j.slice().type, + j.slice().base, + j.slice().xStride, + j.slice().yStride, + false, // zero + (j.slice().xTileCoords)? 1: 0, + (j.slice().yTileCoords)? 1: 0)); + } + } + + // + // Store the new frame buffer. + // + + _data->frameBuffer = frameBuffer; + _data->slices = slices; +} + + +const FrameBuffer & +TiledOutputFile::frameBuffer () const +{ + Lock lock (*_data); + return _data->frameBuffer; +} + + +void +TiledOutputFile::writeTiles (int dx1, int dx2, int dy1, int dy2, + int lx, int ly) +{ + try + { + Lock lock (*_data); + + if (_data->slices.size() == 0) + throw Iex::ArgExc ("No frame buffer specified " + "as pixel data source."); + + if (!isValidTile (dx1, dy1, lx, ly) || !isValidTile (dx2, dy2, lx, ly)) + throw Iex::ArgExc ("Tile coordinates are invalid."); + + // + // Determine the first and last tile coordinates in both dimensions + // based on the file's lineOrder + // + + if (dx1 > dx2) + swap (dx1, dx2); + + if (dy1 > dy2) + swap (dy1, dy2); + + int dyStart = dy1; + int dyStop = dy2 + 1; + int dY = 1; + + if (_data->lineOrder == DECREASING_Y) + { + dyStart = dy2; + dyStop = dy1 - 1; + dY = -1; + } + + int numTiles = (dx2 - dx1 + 1) * (dy2 - dy1 + 1); + int numTasks = min ((int)_data->tileBuffers.size(), numTiles); + + // + // Create a task group for all tile buffer tasks. When the + // task group goes out of scope, the destructor waits until + // all tasks are complete. + // + + { + TaskGroup taskGroup; + + // + // Add in the initial compression tasks to the thread pool + // + + int nextCompBuffer = 0; + int dxComp = dx1; + int dyComp = dyStart; + + while (nextCompBuffer < numTasks) + { + ThreadPool::addGlobalTask (new TileBufferTask (&taskGroup, + _data, + nextCompBuffer++, + dxComp, dyComp, + lx, ly)); + dxComp++; + + if (dxComp > dx2) + { + dxComp = dx1; + dyComp += dY; + } + } + + // + // Write the compressed buffers and add in more compression + // tasks until done + // + + int nextWriteBuffer = 0; + int dxWrite = dx1; + int dyWrite = dyStart; + + while (nextWriteBuffer < numTiles) + { + // + // Wait until the nextWriteBuffer is ready to be written + // + + TileBuffer* writeBuffer = + _data->getTileBuffer (nextWriteBuffer); + + writeBuffer->wait(); + + // + // Write the tilebuffer + // + + bufferedTileWrite (_data, dxWrite, dyWrite, lx, ly, + writeBuffer->dataPtr, + writeBuffer->dataSize); + + // + // Release the lock on nextWriteBuffer + // + + writeBuffer->post(); + + // + // If there are no more tileBuffers to compress, then + // only continue to write out remaining tileBuffers, + // otherwise keep adding compression tasks. + // + + if (nextCompBuffer < numTiles) + { + // + // add nextCompBuffer as a compression Task + // + + ThreadPool::addGlobalTask + (new TileBufferTask (&taskGroup, + _data, + nextCompBuffer, + dxComp, dyComp, + lx, ly)); + } + + nextWriteBuffer++; + dxWrite++; + + if (dxWrite > dx2) + { + dxWrite = dx1; + dyWrite += dY; + } + + nextCompBuffer++; + dxComp++; + + if (dxComp > dx2) + { + dxComp = dx1; + dyComp += dY; + } + } + + // + // finish all tasks + // + } + + // + // Exeption handling: + // + // TileBufferTask::execute() may have encountered exceptions, but + // those exceptions occurred in another thread, not in the thread + // that is executing this call to TiledOutputFile::writeTiles(). + // TileBufferTask::execute() has caught all exceptions and stored + // the exceptions' what() strings in the tile buffers. + // Now we check if any tile buffer contains a stored exception; if + // this is the case then we re-throw the exception in this thread. + // (It is possible that multiple tile buffers contain stored + // exceptions. We re-throw the first exception we find and + // ignore all others.) + // + + const string *exception = 0; + + for (int i = 0; i < _data->tileBuffers.size(); ++i) + { + TileBuffer *tileBuffer = _data->tileBuffers[i]; + + if (tileBuffer->hasException && !exception) + exception = &tileBuffer->exception; + + tileBuffer->hasException = false; + } + + if (exception) + throw Iex::IoExc (*exception); + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Failed to write pixel data to image " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +void +TiledOutputFile::writeTiles (int dx1, int dxMax, int dyMin, int dyMax, int l) +{ + writeTiles (dx1, dxMax, dyMin, dyMax, l, l); +} + + +void +TiledOutputFile::writeTile (int dx, int dy, int lx, int ly) +{ + writeTiles (dx, dx, dy, dy, lx, ly); +} + + +void +TiledOutputFile::writeTile (int dx, int dy, int l) +{ + writeTile(dx, dy, l, l); +} + + +void +TiledOutputFile::copyPixels (TiledInputFile &in) +{ + Lock lock (*_data); + + // + // Check if this file's and and the InputFile's + // headers are compatible. + // + + const Header &hdr = _data->header; + const Header &inHdr = in.header(); + + if (!hdr.hasTileDescription() || !inHdr.hasTileDescription()) + THROW (Iex::ArgExc, "Cannot perform a quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\". The " + "output file is tiled, but the input file is not. " + "Try using OutputFile::copyPixels() instead."); + + if (!(hdr.tileDescription() == inHdr.tileDescription())) + THROW (Iex::ArgExc, "Quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\" failed. " + "The files have different tile descriptions."); + + if (!(hdr.dataWindow() == inHdr.dataWindow())) + THROW (Iex::ArgExc, "Cannot copy pixels from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\". The " + "files have different data windows."); + + if (!(hdr.lineOrder() == inHdr.lineOrder())) + THROW (Iex::ArgExc, "Quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\" failed. " + "The files have different line orders."); + + if (!(hdr.compression() == inHdr.compression())) + THROW (Iex::ArgExc, "Quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\" failed. " + "The files use different compression methods."); + + if (!(hdr.channels() == inHdr.channels())) + THROW (Iex::ArgExc, "Quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << fileName() << "\" " + "failed. The files have different channel " + "lists."); + + // + // Verify that no pixel data have been written to this file yet. + // + + if (!_data->tileOffsets.isEmpty()) + THROW (Iex::LogicExc, "Quick pixel copy from image " + "file \"" << in.fileName() << "\" to image " + "file \"" << _data->os->fileName() << "\" " + "failed. \"" << fileName() << "\" " + "already contains pixel data."); + + // + // Calculate the total number of tiles in the file + // + + int numAllTiles = 0; + + switch (levelMode ()) + { + case ONE_LEVEL: + case MIPMAP_LEVELS: + + for (size_t i_l = 0; i_l < numLevels (); ++i_l) + numAllTiles += numXTiles (i_l) * numYTiles (i_l); + + break; + + case RIPMAP_LEVELS: + + for (size_t i_ly = 0; i_ly < numYLevels (); ++i_ly) + for (size_t i_lx = 0; i_lx < numXLevels (); ++i_lx) + numAllTiles += numXTiles (i_lx) * numYTiles (i_ly); + + break; + + default: + + throw Iex::ArgExc ("Unknown LevelMode format."); + } + + for (int i = 0; i < numAllTiles; ++i) + { + const char *pixelData; + int pixelDataSize; + + int dx = _data->nextTileToWrite.dx; + int dy = _data->nextTileToWrite.dy; + int lx = _data->nextTileToWrite.lx; + int ly = _data->nextTileToWrite.ly; + + in.rawTileData (dx, dy, lx, ly, pixelData, pixelDataSize); + writeTileData (_data, dx, dy, lx, ly, pixelData, pixelDataSize); + } +} + + +void +TiledOutputFile::copyPixels (InputFile &in) +{ + copyPixels (*in.tFile()); +} + + +unsigned int +TiledOutputFile::tileXSize () const +{ + return _data->tileDesc.xSize; +} + + +unsigned int +TiledOutputFile::tileYSize () const +{ + return _data->tileDesc.ySize; +} + + +LevelMode +TiledOutputFile::levelMode () const +{ + return _data->tileDesc.mode; +} + + +LevelRoundingMode +TiledOutputFile::levelRoundingMode () const +{ + return _data->tileDesc.roundingMode; +} + + +int +TiledOutputFile::numLevels () const +{ + if (levelMode() == RIPMAP_LEVELS) + THROW (Iex::LogicExc, "Error calling numLevels() on image " + "file \"" << fileName() << "\" " + "(numLevels() is not defined for RIPMAPs)."); + return _data->numXLevels; +} + + +int +TiledOutputFile::numXLevels () const +{ + return _data->numXLevels; +} + + +int +TiledOutputFile::numYLevels () const +{ + return _data->numYLevels; +} + + +bool +TiledOutputFile::isValidLevel (int lx, int ly) const +{ + if (lx < 0 || ly < 0) + return false; + + if (levelMode() == MIPMAP_LEVELS && lx != ly) + return false; + + if (lx >= numXLevels() || ly >= numYLevels()) + return false; + + return true; +} + + +int +TiledOutputFile::levelWidth (int lx) const +{ + try + { + int retVal = levelSize (_data->minX, _data->maxX, lx, + _data->tileDesc.roundingMode); + + return retVal; + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Error calling levelWidth() on image " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +int +TiledOutputFile::levelHeight (int ly) const +{ + try + { + return levelSize (_data->minY, _data->maxY, ly, + _data->tileDesc.roundingMode); + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Error calling levelHeight() on image " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +int +TiledOutputFile::numXTiles (int lx) const +{ + if (lx < 0 || lx >= _data->numXLevels) + THROW (Iex::LogicExc, "Error calling numXTiles() on image " + "file \"" << _data->os->fileName() << "\" " + "(Argument is not in valid range)."); + + return _data->numXTiles[lx]; +} + + +int +TiledOutputFile::numYTiles (int ly) const +{ + if (ly < 0 || ly >= _data->numYLevels) + THROW (Iex::LogicExc, "Error calling numXTiles() on image " + "file \"" << _data->os->fileName() << "\" " + "(Argument is not in valid range)."); + + return _data->numYTiles[ly]; +} + + +Box2i +TiledOutputFile::dataWindowForLevel (int l) const +{ + return dataWindowForLevel (l, l); +} + + +Box2i +TiledOutputFile::dataWindowForLevel (int lx, int ly) const +{ + try + { + return Imf::dataWindowForLevel (_data->tileDesc, + _data->minX, _data->maxX, + _data->minY, _data->maxY, + lx, ly); + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Error calling dataWindowForLevel() on image " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +Box2i +TiledOutputFile::dataWindowForTile (int dx, int dy, int l) const +{ + return dataWindowForTile (dx, dy, l, l); +} + + +Box2i +TiledOutputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const +{ + try + { + if (!isValidTile (dx, dy, lx, ly)) + throw Iex::ArgExc ("Arguments not in valid range."); + + return Imf::dataWindowForTile (_data->tileDesc, + _data->minX, _data->maxX, + _data->minY, _data->maxY, + dx, dy, + lx, ly); + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Error calling dataWindowForTile() on image " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +bool +TiledOutputFile::isValidTile (int dx, int dy, int lx, int ly) const +{ + return ((lx < _data->numXLevels && lx >= 0) && + (ly < _data->numYLevels && ly >= 0) && + (dx < _data->numXTiles[lx] && dx >= 0) && + (dy < _data->numYTiles[ly] && dy >= 0)); +} + + +void +TiledOutputFile::updatePreviewImage (const PreviewRgba newPixels[]) +{ + Lock lock (*_data); + + if (_data->previewPosition <= 0) + THROW (Iex::LogicExc, "Cannot update preview image pixels. " + "File \"" << fileName() << "\" does not " + "contain a preview image."); + + // + // Store the new pixels in the header's preview image attribute. + // + + PreviewImageAttribute &pia = + _data->header.typedAttribute <PreviewImageAttribute> ("preview"); + + PreviewImage &pi = pia.value(); + PreviewRgba *pixels = pi.pixels(); + int numPixels = pi.width() * pi.height(); + + for (int i = 0; i < numPixels; ++i) + pixels[i] = newPixels[i]; + + // + // Save the current file position, jump to the position in + // the file where the preview image starts, store the new + // preview image, and jump back to the saved file position. + // + + Int64 savedPosition = _data->os->tellp(); + + try + { + _data->os->seekp (_data->previewPosition); + pia.writeValueTo (*_data->os, _data->version); + _data->os->seekp (savedPosition); + } + catch (Iex::BaseExc &e) + { + REPLACE_EXC (e, "Cannot update preview image pixels for " + "file \"" << fileName() << "\". " << e); + throw; + } +} + + +void +TiledOutputFile::breakTile + (int dx, int dy, + int lx, int ly, + int offset, + int length, + char c) +{ + Lock lock (*_data); + + Int64 position = _data->tileOffsets (dx, dy, lx, ly); + + if (!position) + THROW (Iex::ArgExc, + "Cannot overwrite tile " + "(" << dx << ", " << dy << ", " << lx << "," << ly << "). " + "The tile has not yet been stored in " + "file \"" << fileName() << "\"."); + + _data->currentPosition = 0; + _data->os->seekp (position + offset); + + for (int i = 0; i < length; ++i) + _data->os->write (&c, 1); +} + +} // namespace Imf |