/////////////////////////////////////////////////////////////////////////// // // 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 TiledRgbaOutputFile // class TiledRgbaInputFile // //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include "IlmThreadMutex.h" #include "Iex.h" namespace Imf { using namespace std; using namespace Imath; using namespace RgbaYca; using namespace IlmThread; namespace { void insertChannels (Header &header, RgbaChannels rgbaChannels, const char fileName[]) { ChannelList ch; if (rgbaChannels & (WRITE_Y | WRITE_C)) { if (rgbaChannels & WRITE_Y) { ch.insert ("Y", Channel (HALF, 1, 1)); } if (rgbaChannels & WRITE_C) { THROW (Iex::ArgExc, "Cannot open file \"" << fileName << "\" " "for writing. Tiled image files do not " "support subsampled chroma channels."); } } else { if (rgbaChannels & WRITE_R) ch.insert ("R", Channel (HALF, 1, 1)); if (rgbaChannels & WRITE_G) ch.insert ("G", Channel (HALF, 1, 1)); if (rgbaChannels & WRITE_B) ch.insert ("B", Channel (HALF, 1, 1)); } if (rgbaChannels & WRITE_A) ch.insert ("A", Channel (HALF, 1, 1)); header.channels() = ch; } RgbaChannels rgbaChannels (const ChannelList &ch, const string &channelNamePrefix = "") { int i = 0; if (ch.findChannel (channelNamePrefix + "R")) i |= WRITE_R; if (ch.findChannel (channelNamePrefix + "G")) i |= WRITE_G; if (ch.findChannel (channelNamePrefix + "B")) i |= WRITE_B; if (ch.findChannel (channelNamePrefix + "A")) i |= WRITE_A; if (ch.findChannel (channelNamePrefix + "Y")) i |= WRITE_Y; return RgbaChannels (i); } string prefixFromLayerName (const string &layerName, const Header &header) { if (layerName.empty()) return ""; if (hasMultiView (header) && multiView(header)[0] == layerName) return ""; return layerName + "."; } V3f ywFromHeader (const Header &header) { Chromaticities cr; if (hasChromaticities (header)) cr = chromaticities (header); return computeYw (cr); } } // namespace class TiledRgbaOutputFile::ToYa: public Mutex { public: ToYa (TiledOutputFile &outputFile, RgbaChannels rgbaChannels); void setFrameBuffer (const Rgba *base, size_t xStride, size_t yStride); void writeTile (int dx, int dy, int lx, int ly); private: TiledOutputFile & _outputFile; bool _writeA; unsigned int _tileXSize; unsigned int _tileYSize; V3f _yw; Array2D _buf; const Rgba * _fbBase; size_t _fbXStride; size_t _fbYStride; }; TiledRgbaOutputFile::ToYa::ToYa (TiledOutputFile &outputFile, RgbaChannels rgbaChannels) : _outputFile (outputFile) { _writeA = (rgbaChannels & WRITE_A)? true: false; const TileDescription &td = outputFile.header().tileDescription(); _tileXSize = td.xSize; _tileYSize = td.ySize; _yw = ywFromHeader (_outputFile.header()); _buf.resizeErase (_tileYSize, _tileXSize); _fbBase = 0; _fbXStride = 0; _fbYStride = 0; } void TiledRgbaOutputFile::ToYa::setFrameBuffer (const Rgba *base, size_t xStride, size_t yStride) { _fbBase = base; _fbXStride = xStride; _fbYStride = yStride; } void TiledRgbaOutputFile::ToYa::writeTile (int dx, int dy, int lx, int ly) { if (_fbBase == 0) { THROW (Iex::ArgExc, "No frame buffer was specified as the " "pixel data source for image file " "\"" << _outputFile.fileName() << "\"."); } // // Copy the tile's RGBA pixels into _buf and convert // them to luminance/alpha format // Box2i dw = _outputFile.dataWindowForTile (dx, dy, lx, ly); int width = dw.max.x - dw.min.x + 1; for (int y = dw.min.y, y1 = 0; y <= dw.max.y; ++y, ++y1) { for (int x = dw.min.x, x1 = 0; x <= dw.max.x; ++x, ++x1) _buf[y1][x1] = _fbBase[x * _fbXStride + y * _fbYStride]; RGBAtoYCA (_yw, width, _writeA, _buf[y1], _buf[y1]); } // // Store the contents of _buf in the output file // FrameBuffer fb; fb.insert ("Y", Slice (HALF, // type (char *) &_buf[-dw.min.y][-dw.min.x].g, // base sizeof (Rgba), // xStride sizeof (Rgba) * _tileXSize)); // yStride fb.insert ("A", Slice (HALF, // type (char *) &_buf[-dw.min.y][-dw.min.x].a, // base sizeof (Rgba), // xStride sizeof (Rgba) * _tileXSize)); // yStride _outputFile.setFrameBuffer (fb); _outputFile.writeTile (dx, dy, lx, ly); } TiledRgbaOutputFile::TiledRgbaOutputFile (const char name[], const Header &header, RgbaChannels rgbaChannels, int tileXSize, int tileYSize, LevelMode mode, LevelRoundingMode rmode, int numThreads) : _outputFile (0), _toYa (0) { Header hd (header); insertChannels (hd, rgbaChannels, name); hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode)); _outputFile = new TiledOutputFile (name, hd, numThreads); if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels); } TiledRgbaOutputFile::TiledRgbaOutputFile (OStream &os, const Header &header, RgbaChannels rgbaChannels, int tileXSize, int tileYSize, LevelMode mode, LevelRoundingMode rmode, int numThreads) : _outputFile (0), _toYa (0) { Header hd (header); insertChannels (hd, rgbaChannels, os.fileName()); hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode)); _outputFile = new TiledOutputFile (os, hd, numThreads); if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels); } TiledRgbaOutputFile::TiledRgbaOutputFile (const char name[], int tileXSize, int tileYSize, LevelMode mode, LevelRoundingMode rmode, const Imath::Box2i &displayWindow, const Imath::Box2i &dataWindow, RgbaChannels rgbaChannels, float pixelAspectRatio, const Imath::V2f screenWindowCenter, float screenWindowWidth, LineOrder lineOrder, Compression compression, int numThreads) : _outputFile (0), _toYa (0) { Header hd (displayWindow, dataWindow.isEmpty()? displayWindow: dataWindow, pixelAspectRatio, screenWindowCenter, screenWindowWidth, lineOrder, compression); insertChannels (hd, rgbaChannels, name); hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode)); _outputFile = new TiledOutputFile (name, hd, numThreads); if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels); } TiledRgbaOutputFile::TiledRgbaOutputFile (const char name[], int width, int height, int tileXSize, int tileYSize, LevelMode mode, LevelRoundingMode rmode, RgbaChannels rgbaChannels, float pixelAspectRatio, const Imath::V2f screenWindowCenter, float screenWindowWidth, LineOrder lineOrder, Compression compression, int numThreads) : _outputFile (0), _toYa (0) { Header hd (width, height, pixelAspectRatio, screenWindowCenter, screenWindowWidth, lineOrder, compression); insertChannels (hd, rgbaChannels, name); hd.setTileDescription (TileDescription (tileXSize, tileYSize, mode, rmode)); _outputFile = new TiledOutputFile (name, hd, numThreads); if (rgbaChannels & WRITE_Y) _toYa = new ToYa (*_outputFile, rgbaChannels); } TiledRgbaOutputFile::~TiledRgbaOutputFile () { delete _outputFile; delete _toYa; } void TiledRgbaOutputFile::setFrameBuffer (const Rgba *base, size_t xStride, size_t yStride) { if (_toYa) { Lock lock (*_toYa); _toYa->setFrameBuffer (base, xStride, yStride); } else { size_t xs = xStride * sizeof (Rgba); size_t ys = yStride * sizeof (Rgba); FrameBuffer fb; fb.insert ("R", Slice (HALF, (char *) &base[0].r, xs, ys)); fb.insert ("G", Slice (HALF, (char *) &base[0].g, xs, ys)); fb.insert ("B", Slice (HALF, (char *) &base[0].b, xs, ys)); fb.insert ("A", Slice (HALF, (char *) &base[0].a, xs, ys)); _outputFile->setFrameBuffer (fb); } } const Header & TiledRgbaOutputFile::header () const { return _outputFile->header(); } const FrameBuffer & TiledRgbaOutputFile::frameBuffer () const { return _outputFile->frameBuffer(); } const Imath::Box2i & TiledRgbaOutputFile::displayWindow () const { return _outputFile->header().displayWindow(); } const Imath::Box2i & TiledRgbaOutputFile::dataWindow () const { return _outputFile->header().dataWindow(); } float TiledRgbaOutputFile::pixelAspectRatio () const { return _outputFile->header().pixelAspectRatio(); } const Imath::V2f TiledRgbaOutputFile::screenWindowCenter () const { return _outputFile->header().screenWindowCenter(); } float TiledRgbaOutputFile::screenWindowWidth () const { return _outputFile->header().screenWindowWidth(); } LineOrder TiledRgbaOutputFile::lineOrder () const { return _outputFile->header().lineOrder(); } Compression TiledRgbaOutputFile::compression () const { return _outputFile->header().compression(); } RgbaChannels TiledRgbaOutputFile::channels () const { return rgbaChannels (_outputFile->header().channels()); } unsigned int TiledRgbaOutputFile::tileXSize () const { return _outputFile->tileXSize(); } unsigned int TiledRgbaOutputFile::tileYSize () const { return _outputFile->tileYSize(); } LevelMode TiledRgbaOutputFile::levelMode () const { return _outputFile->levelMode(); } LevelRoundingMode TiledRgbaOutputFile::levelRoundingMode () const { return _outputFile->levelRoundingMode(); } int TiledRgbaOutputFile::numLevels () const { return _outputFile->numLevels(); } int TiledRgbaOutputFile::numXLevels () const { return _outputFile->numXLevels(); } int TiledRgbaOutputFile::numYLevels () const { return _outputFile->numYLevels(); } bool TiledRgbaOutputFile::isValidLevel (int lx, int ly) const { return _outputFile->isValidLevel (lx, ly); } int TiledRgbaOutputFile::levelWidth (int lx) const { return _outputFile->levelWidth (lx); } int TiledRgbaOutputFile::levelHeight (int ly) const { return _outputFile->levelHeight (ly); } int TiledRgbaOutputFile::numXTiles (int lx) const { return _outputFile->numXTiles (lx); } int TiledRgbaOutputFile::numYTiles (int ly) const { return _outputFile->numYTiles (ly); } Imath::Box2i TiledRgbaOutputFile::dataWindowForLevel (int l) const { return _outputFile->dataWindowForLevel (l); } Imath::Box2i TiledRgbaOutputFile::dataWindowForLevel (int lx, int ly) const { return _outputFile->dataWindowForLevel (lx, ly); } Imath::Box2i TiledRgbaOutputFile::dataWindowForTile (int dx, int dy, int l) const { return _outputFile->dataWindowForTile (dx, dy, l); } Imath::Box2i TiledRgbaOutputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const { return _outputFile->dataWindowForTile (dx, dy, lx, ly); } void TiledRgbaOutputFile::writeTile (int dx, int dy, int l) { if (_toYa) { Lock lock (*_toYa); _toYa->writeTile (dx, dy, l, l); } else { _outputFile->writeTile (dx, dy, l); } } void TiledRgbaOutputFile::writeTile (int dx, int dy, int lx, int ly) { if (_toYa) { Lock lock (*_toYa); _toYa->writeTile (dx, dy, lx, ly); } else { _outputFile->writeTile (dx, dy, lx, ly); } } void TiledRgbaOutputFile::writeTiles (int dxMin, int dxMax, int dyMin, int dyMax, int lx, int ly) { if (_toYa) { Lock lock (*_toYa); for (int dy = dyMin; dy <= dyMax; dy++) for (int dx = dxMin; dx <= dxMax; dx++) _toYa->writeTile (dx, dy, lx, ly); } else { _outputFile->writeTiles (dxMin, dxMax, dyMin, dyMax, lx, ly); } } void TiledRgbaOutputFile::writeTiles (int dxMin, int dxMax, int dyMin, int dyMax, int l) { writeTiles (dxMin, dxMax, dyMin, dyMax, l, l); } class TiledRgbaInputFile::FromYa: public Mutex { public: FromYa (TiledInputFile &inputFile); void setFrameBuffer (Rgba *base, size_t xStride, size_t yStride, const string &channelNamePrefix); void readTile (int dx, int dy, int lx, int ly); private: TiledInputFile & _inputFile; unsigned int _tileXSize; unsigned int _tileYSize; V3f _yw; Array2D _buf; Rgba * _fbBase; size_t _fbXStride; size_t _fbYStride; }; TiledRgbaInputFile::FromYa::FromYa (TiledInputFile &inputFile) : _inputFile (inputFile) { const TileDescription &td = inputFile.header().tileDescription(); _tileXSize = td.xSize; _tileYSize = td.ySize; _yw = ywFromHeader (_inputFile.header()); _buf.resizeErase (_tileYSize, _tileXSize); _fbBase = 0; _fbXStride = 0; _fbYStride = 0; } void TiledRgbaInputFile::FromYa::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride, const string &channelNamePrefix) { if (_fbBase == 0) { FrameBuffer fb; fb.insert (channelNamePrefix + "Y", Slice (HALF, // type (char *) &_buf[0][0].g, // base sizeof (Rgba), // xStride sizeof (Rgba) * _tileXSize, // yStride 1, 1, // sampling 0.0, // fillValue true, true)); // tileCoordinates fb.insert (channelNamePrefix + "A", Slice (HALF, // type (char *) &_buf[0][0].a, // base sizeof (Rgba), // xStride sizeof (Rgba) * _tileXSize, // yStride 1, 1, // sampling 1.0, // fillValue true, true)); // tileCoordinates _inputFile.setFrameBuffer (fb); } _fbBase = base; _fbXStride = xStride; _fbYStride = yStride; } void TiledRgbaInputFile::FromYa::readTile (int dx, int dy, int lx, int ly) { if (_fbBase == 0) { THROW (Iex::ArgExc, "No frame buffer was specified as the " "pixel data destination for image file " "\"" << _inputFile.fileName() << "\"."); } // // Read the tile requested by the caller into _buf. // _inputFile.readTile (dx, dy, lx, ly); // // Convert the luminance/alpha pixels to RGBA // and copy them into the caller's frame buffer. // Box2i dw = _inputFile.dataWindowForTile (dx, dy, lx, ly); int width = dw.max.x - dw.min.x + 1; for (int y = dw.min.y, y1 = 0; y <= dw.max.y; ++y, ++y1) { for (int x1 = 0; x1 < width; ++x1) { _buf[y1][x1].r = 0; _buf[y1][x1].b = 0; } YCAtoRGBA (_yw, width, _buf[y1], _buf[y1]); for (int x = dw.min.x, x1 = 0; x <= dw.max.x; ++x, ++x1) { _fbBase[x * _fbXStride + y * _fbYStride] = _buf[y1][x1]; } } } TiledRgbaInputFile::TiledRgbaInputFile (const char name[], int numThreads): _inputFile (new TiledInputFile (name, numThreads)), _fromYa (0), _channelNamePrefix ("") { if (channels() & WRITE_Y) _fromYa = new FromYa (*_inputFile); } TiledRgbaInputFile::TiledRgbaInputFile (IStream &is, int numThreads): _inputFile (new TiledInputFile (is, numThreads)), _fromYa (0), _channelNamePrefix ("") { if (channels() & WRITE_Y) _fromYa = new FromYa (*_inputFile); } TiledRgbaInputFile::TiledRgbaInputFile (const char name[], const string &layerName, int numThreads) : _inputFile (new TiledInputFile (name, numThreads)), _fromYa (0), _channelNamePrefix (prefixFromLayerName (layerName, _inputFile->header())) { if (channels() & WRITE_Y) _fromYa = new FromYa (*_inputFile); } TiledRgbaInputFile::TiledRgbaInputFile (IStream &is, const string &layerName, int numThreads) : _inputFile (new TiledInputFile (is, numThreads)), _fromYa (0), _channelNamePrefix (prefixFromLayerName (layerName, _inputFile->header())) { if (channels() & WRITE_Y) _fromYa = new FromYa (*_inputFile); } TiledRgbaInputFile::~TiledRgbaInputFile () { delete _inputFile; delete _fromYa; } void TiledRgbaInputFile::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride) { if (_fromYa) { Lock lock (*_fromYa); _fromYa->setFrameBuffer (base, xStride, yStride, _channelNamePrefix); } else { size_t xs = xStride * sizeof (Rgba); size_t ys = yStride * sizeof (Rgba); FrameBuffer fb; fb.insert (_channelNamePrefix + "R", Slice (HALF, (char *) &base[0].r, xs, ys, 1, 1, // xSampling, ySampling 0.0)); // fillValue fb.insert (_channelNamePrefix + "G", Slice (HALF, (char *) &base[0].g, xs, ys, 1, 1, // xSampling, ySampling 0.0)); // fillValue fb.insert (_channelNamePrefix + "B", Slice (HALF, (char *) &base[0].b, xs, ys, 1, 1, // xSampling, ySampling 0.0)); // fillValue fb.insert (_channelNamePrefix + "A", Slice (HALF, (char *) &base[0].a, xs, ys, 1, 1, // xSampling, ySampling 1.0)); // fillValue _inputFile->setFrameBuffer (fb); } } void TiledRgbaInputFile::setLayerName (const std::string &layerName) { delete _fromYa; _fromYa = 0; _channelNamePrefix = prefixFromLayerName (layerName, _inputFile->header()); if (channels() & WRITE_Y) _fromYa = new FromYa (*_inputFile); FrameBuffer fb; _inputFile->setFrameBuffer (fb); } const Header & TiledRgbaInputFile::header () const { return _inputFile->header(); } const char * TiledRgbaInputFile::fileName () const { return _inputFile->fileName(); } const FrameBuffer & TiledRgbaInputFile::frameBuffer () const { return _inputFile->frameBuffer(); } const Imath::Box2i & TiledRgbaInputFile::displayWindow () const { return _inputFile->header().displayWindow(); } const Imath::Box2i & TiledRgbaInputFile::dataWindow () const { return _inputFile->header().dataWindow(); } float TiledRgbaInputFile::pixelAspectRatio () const { return _inputFile->header().pixelAspectRatio(); } const Imath::V2f TiledRgbaInputFile::screenWindowCenter () const { return _inputFile->header().screenWindowCenter(); } float TiledRgbaInputFile::screenWindowWidth () const { return _inputFile->header().screenWindowWidth(); } LineOrder TiledRgbaInputFile::lineOrder () const { return _inputFile->header().lineOrder(); } Compression TiledRgbaInputFile::compression () const { return _inputFile->header().compression(); } RgbaChannels TiledRgbaInputFile::channels () const { return rgbaChannels (_inputFile->header().channels(), _channelNamePrefix); } int TiledRgbaInputFile::version () const { return _inputFile->version(); } bool TiledRgbaInputFile::isComplete () const { return _inputFile->isComplete(); } unsigned int TiledRgbaInputFile::tileXSize () const { return _inputFile->tileXSize(); } unsigned int TiledRgbaInputFile::tileYSize () const { return _inputFile->tileYSize(); } LevelMode TiledRgbaInputFile::levelMode () const { return _inputFile->levelMode(); } LevelRoundingMode TiledRgbaInputFile::levelRoundingMode () const { return _inputFile->levelRoundingMode(); } int TiledRgbaInputFile::numLevels () const { return _inputFile->numLevels(); } int TiledRgbaInputFile::numXLevels () const { return _inputFile->numXLevels(); } int TiledRgbaInputFile::numYLevels () const { return _inputFile->numYLevels(); } bool TiledRgbaInputFile::isValidLevel (int lx, int ly) const { return _inputFile->isValidLevel (lx, ly); } int TiledRgbaInputFile::levelWidth (int lx) const { return _inputFile->levelWidth (lx); } int TiledRgbaInputFile::levelHeight (int ly) const { return _inputFile->levelHeight (ly); } int TiledRgbaInputFile::numXTiles (int lx) const { return _inputFile->numXTiles(lx); } int TiledRgbaInputFile::numYTiles (int ly) const { return _inputFile->numYTiles(ly); } Imath::Box2i TiledRgbaInputFile::dataWindowForLevel (int l) const { return _inputFile->dataWindowForLevel (l); } Imath::Box2i TiledRgbaInputFile::dataWindowForLevel (int lx, int ly) const { return _inputFile->dataWindowForLevel (lx, ly); } Imath::Box2i TiledRgbaInputFile::dataWindowForTile (int dx, int dy, int l) const { return _inputFile->dataWindowForTile (dx, dy, l); } Imath::Box2i TiledRgbaInputFile::dataWindowForTile (int dx, int dy, int lx, int ly) const { return _inputFile->dataWindowForTile (dx, dy, lx, ly); } void TiledRgbaInputFile::readTile (int dx, int dy, int l) { if (_fromYa) { Lock lock (*_fromYa); _fromYa->readTile (dx, dy, l, l); } else { _inputFile->readTile (dx, dy, l); } } void TiledRgbaInputFile::readTile (int dx, int dy, int lx, int ly) { if (_fromYa) { Lock lock (*_fromYa); _fromYa->readTile (dx, dy, lx, ly); } else { _inputFile->readTile (dx, dy, lx, ly); } } void TiledRgbaInputFile::readTiles (int dxMin, int dxMax, int dyMin, int dyMax, int lx, int ly) { if (_fromYa) { Lock lock (*_fromYa); for (int dy = dyMin; dy <= dyMax; dy++) for (int dx = dxMin; dx <= dxMax; dx++) _fromYa->readTile (dx, dy, lx, ly); } else { _inputFile->readTiles (dxMin, dxMax, dyMin, dyMax, lx, ly); } } void TiledRgbaInputFile::readTiles (int dxMin, int dxMax, int dyMin, int dyMax, int l) { readTiles (dxMin, dxMax, dyMin, dyMax, l, l); } void TiledRgbaOutputFile::updatePreviewImage (const PreviewRgba newPixels[]) { _outputFile->updatePreviewImage (newPixels); } void TiledRgbaOutputFile::breakTile (int dx, int dy, int lx, int ly, int offset, int length, char c) { _outputFile->breakTile (dx, dy, lx, ly, offset, length, c); } } // namespace Imf