/////////////////////////////////////////////////////////////////////////// // // Copyright (c) 2006, 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 B44Compressor // // This compressor is lossy for HALF channels; the compression rate // is fixed at 32/14 (approximately 2.28). FLOAT and UINT channels // are not compressed; their data are preserved exactly. // // Each HALF channel is split into blocks of 4 by 4 pixels. An // uncompressed block occupies 32 bytes, which are re-interpreted // as sixteen 16-bit unsigned integers, t[0] ... t[15]. Compression // shrinks the block to 14 bytes. The compressed 14-byte block // contains // // - t[0] // // - a 6-bit shift value // // - 15 densely packed 6-bit values, r[0] ... r[14], which are // computed by subtracting adjacent pixel values and right- // shifting the differences according to the stored shift value. // // Differences between adjacent pixels are computed according // to the following diagram: // // 0 --------> 1 --------> 2 --------> 3 // | 3 7 11 // | // | 0 // | // v // 4 --------> 5 --------> 6 --------> 7 // | 4 8 12 // | // | 1 // | // v // 8 --------> 9 --------> 10 --------> 11 // | 5 9 13 // | // | 2 // | // v // 12 --------> 13 --------> 14 --------> 15 // 6 10 14 // // Here // // 5 ---------> 6 // 8 // // means that r[8] is the difference between t[5] and t[6]. // // - optionally, a 4-by-4 pixel block where all pixels have the // same value can be treated as a special case, where the // compressed block contains only 3 instead of 14 bytes: // t[0], followed by an "impossible" 6-bit shift value and // two padding bits. // // This compressor can handle positive and negative pixel values. // NaNs and infinities are replaced with zeroes before compression. // //----------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Imf { using Imath::divp; using Imath::modp; using Imath::Box2i; using Imath::V2i; using std::min; namespace { // // Lookup tables for // y = exp (x / 8) // and // x = 8 * log (y) // #include "b44ExpLogTable.h" inline void convertFromLinear (unsigned short s[16]) { for (int i = 0; i < 16; ++i) s[i] = expTable[s[i]]; } inline void convertToLinear (unsigned short s[16]) { for (int i = 0; i < 16; ++i) s[i] = logTable[s[i]]; } inline int shiftAndRound (int x, int shift) { // // Compute // // y = x * pow (2, -shift), // // then round y to the nearest integer. // In case of a tie, where y is exactly // halfway between two integers, round // to the even one. // x <<= 1; int a = (1 << shift) - 1; shift += 1; int b = (x >> shift) & 1; return (x + a + b) >> shift; } int pack (const unsigned short s[16], unsigned char b[14], bool optFlatFields, bool exactMax) { // // Pack a block of 4 by 4 16-bit pixels (32 bytes) into // either 14 or 3 bytes. // // // Integers s[0] ... s[15] represent floating-point numbers // in what is essentially a sign-magnitude format. Convert // s[0] .. s[15] into a new set of integers, t[0] ... t[15], // such that if t[i] is greater than t[j], the floating-point // number that corresponds to s[i] is always greater than // the floating-point number that corresponds to s[j]. // // Also, replace any bit patterns that represent NaNs or // infinities with bit patterns that represent floating-point // zeroes. // // bit pattern floating-point bit pattern // in s[i] value in t[i] // // 0x7fff NAN 0x8000 // 0x7ffe NAN 0x8000 // ... ... // 0x7c01 NAN 0x8000 // 0x7c00 +infinity 0x8000 // 0x7bff +HALF_MAX 0xfbff // 0x7bfe 0xfbfe // 0x7bfd 0xfbfd // ... ... // 0x0002 +2 * HALF_MIN 0x8002 // 0x0001 +HALF_MIN 0x8001 // 0x0000 +0.0 0x8000 // 0x8000 -0.0 0x7fff // 0x8001 -HALF_MIN 0x7ffe // 0x8002 -2 * HALF_MIN 0x7ffd // ... ... // 0xfbfd 0x0f02 // 0xfbfe 0x0401 // 0xfbff -HALF_MAX 0x0400 // 0xfc00 -infinity 0x8000 // 0xfc01 NAN 0x8000 // ... ... // 0xfffe NAN 0x8000 // 0xffff NAN 0x8000 // unsigned short t[16]; for (int i = 0; i < 16; ++i) { if ((s[i] & 0x7c00) == 0x7c00) t[i] = 0x8000; else if (s[i] & 0x8000) t[i] = ~s[i]; else t[i] = s[i] | 0x8000; } // // Find the maximum, tMax, of t[0] ... t[15]. // unsigned short tMax = 0; for (int i = 0; i < 16; ++i) if (tMax < t[i]) tMax = t[i]; // // Compute a set of running differences, r[0] ... r[14]: // Find a shift value such that after rounding off the // rightmost bits and shifting all differenes are between // -32 and +31. Then bias the differences so that they // end up between 0 and 63. // int shift = -1; int d[16]; int r[15]; int rMin; int rMax; const int bias = 0x20; do { shift += 1; // // Compute absolute differences, d[0] ... d[15], // between tMax and t[0] ... t[15]. // // Shift and round the absolute differences. // for (int i = 0; i < 16; ++i) d[i] = shiftAndRound (tMax - t[i], shift); // // Convert d[0] .. d[15] into running differences // r[ 0] = d[ 0] - d[ 4] + bias; r[ 1] = d[ 4] - d[ 8] + bias; r[ 2] = d[ 8] - d[12] + bias; r[ 3] = d[ 0] - d[ 1] + bias; r[ 4] = d[ 4] - d[ 5] + bias; r[ 5] = d[ 8] - d[ 9] + bias; r[ 6] = d[12] - d[13] + bias; r[ 7] = d[ 1] - d[ 2] + bias; r[ 8] = d[ 5] - d[ 6] + bias; r[ 9] = d[ 9] - d[10] + bias; r[10] = d[13] - d[14] + bias; r[11] = d[ 2] - d[ 3] + bias; r[12] = d[ 6] - d[ 7] + bias; r[13] = d[10] - d[11] + bias; r[14] = d[14] - d[15] + bias; rMin = r[0]; rMax = r[0]; for (int i = 1; i < 15; ++i) { if (rMin > r[i]) rMin = r[i]; if (rMax < r[i]) rMax = r[i]; } } while (rMin < 0 || rMax > 0x3f); if (rMin == bias && rMax == bias && optFlatFields) { // // Special case - all pixels have the same value. // We encode this in 3 instead of 14 bytes by // storing the value 0xfc in the third output byte, // which cannot occur in the 14-byte encoding. // b[0] = (t[0] >> 8); b[1] = t[0]; b[2] = 0xfc; return 3; } if (exactMax) { // // Adjust t[0] so that the pixel whose value is equal // to tMax gets represented as accurately as possible. // t[0] = tMax - (d[0] << shift); } // // Pack t[0], shift and r[0] ... r[14] into 14 bytes: // b[ 0] = (t[0] >> 8); b[ 1] = t[0]; b[ 2] = (unsigned char) ((shift << 2) | (r[ 0] >> 4)); b[ 3] = (unsigned char) ((r[ 0] << 4) | (r[ 1] >> 2)); b[ 4] = (unsigned char) ((r[ 1] << 6) | r[ 2] ); b[ 5] = (unsigned char) ((r[ 3] << 2) | (r[ 4] >> 4)); b[ 6] = (unsigned char) ((r[ 4] << 4) | (r[ 5] >> 2)); b[ 7] = (unsigned char) ((r[ 5] << 6) | r[ 6] ); b[ 8] = (unsigned char) ((r[ 7] << 2) | (r[ 8] >> 4)); b[ 9] = (unsigned char) ((r[ 8] << 4) | (r[ 9] >> 2)); b[10] = (unsigned char) ((r[ 9] << 6) | r[10] ); b[11] = (unsigned char) ((r[11] << 2) | (r[12] >> 4)); b[12] = (unsigned char) ((r[12] << 4) | (r[13] >> 2)); b[13] = (unsigned char) ((r[13] << 6) | r[14] ); return 14; } inline void unpack14 (const unsigned char b[14], unsigned short s[16]) { // // Unpack a 14-byte block into 4 by 4 16-bit pixels. // #if defined (DEBUG) assert (b[2] != 0xfc); #endif s[ 0] = (b[0] << 8) | b[1]; unsigned short shift = (b[ 2] >> 2); unsigned short bias = (0x20 << shift); s[ 4] = s[ 0] + ((((b[ 2] << 4) | (b[ 3] >> 4)) & 0x3f) << shift) - bias; s[ 8] = s[ 4] + ((((b[ 3] << 2) | (b[ 4] >> 6)) & 0x3f) << shift) - bias; s[12] = s[ 8] + ((b[ 4] & 0x3f) << shift) - bias; s[ 1] = s[ 0] + ((b[ 5] >> 2) << shift) - bias; s[ 5] = s[ 4] + ((((b[ 5] << 4) | (b[ 6] >> 4)) & 0x3f) << shift) - bias; s[ 9] = s[ 8] + ((((b[ 6] << 2) | (b[ 7] >> 6)) & 0x3f) << shift) - bias; s[13] = s[12] + ((b[ 7] & 0x3f) << shift) - bias; s[ 2] = s[ 1] + ((b[ 8] >> 2) << shift) - bias; s[ 6] = s[ 5] + ((((b[ 8] << 4) | (b[ 9] >> 4)) & 0x3f) << shift) - bias; s[10] = s[ 9] + ((((b[ 9] << 2) | (b[10] >> 6)) & 0x3f) << shift) - bias; s[14] = s[13] + ((b[10] & 0x3f) << shift) - bias; s[ 3] = s[ 2] + ((b[11] >> 2) << shift) - bias; s[ 7] = s[ 6] + ((((b[11] << 4) | (b[12] >> 4)) & 0x3f) << shift) - bias; s[11] = s[10] + ((((b[12] << 2) | (b[13] >> 6)) & 0x3f) << shift) - bias; s[15] = s[14] + ((b[13] & 0x3f) << shift) - bias; for (int i = 0; i < 16; ++i) { if (s[i] & 0x8000) s[i] &= 0x7fff; else s[i] = ~s[i]; } } inline void unpack3 (const unsigned char b[3], unsigned short s[16]) { // // Unpack a 3-byte block into 4 by 4 identical 16-bit pixels. // #if defined (DEBUG) assert (b[2] == 0xfc); #endif s[0] = (b[0] << 8) | b[1]; if (s[0] & 0x8000) s[0] &= 0x7fff; else s[0] = ~s[0]; for (int i = 1; i < 16; ++i) s[i] = s[0]; } void notEnoughData () { throw Iex::InputExc ("Error decompressing data " "(input data are shorter than expected)."); } void tooMuchData () { throw Iex::InputExc ("Error decompressing data " "(input data are longer than expected)."); } } // namespace struct B44Compressor::ChannelData { unsigned short * start; unsigned short * end; int nx; int ny; int ys; PixelType type; bool pLinear; int size; }; B44Compressor::B44Compressor (const Header &hdr, size_t maxScanLineSize, size_t numScanLines, bool optFlatFields) : Compressor (hdr), _maxScanLineSize (maxScanLineSize), _optFlatFields (optFlatFields), _format (XDR), _numScanLines (numScanLines), _tmpBuffer (0), _outBuffer (0), _numChans (0), _channels (hdr.channels()), _channelData (0) { // // Allocate buffers for compressed an uncompressed pixel data, // allocate a set of ChannelData structs to help speed up the // compress() and uncompress() functions, below, and determine // if uncompressed pixel data should be in native or Xdr format. // _tmpBuffer = new unsigned short [checkArraySize (uiMult (maxScanLineSize, numScanLines), sizeof (unsigned short))]; const ChannelList &channels = header().channels(); int numHalfChans = 0; for (ChannelList::ConstIterator c = channels.begin(); c != channels.end(); ++c) { assert (pixelTypeSize (c.channel().type) % pixelTypeSize (HALF) == 0); ++_numChans; if (c.channel().type == HALF) ++numHalfChans; } // // Compressed data may be larger than the input data // size_t padding = 12 * numHalfChans * (numScanLines + 3) / 4; _outBuffer = new char [uiAdd (uiMult (maxScanLineSize, numScanLines), padding)]; _channelData = new ChannelData[_numChans]; int i = 0; for (ChannelList::ConstIterator c = channels.begin(); c != channels.end(); ++c, ++i) { _channelData[i].ys = c.channel().ySampling; _channelData[i].type = c.channel().type; _channelData[i].pLinear = c.channel().pLinear; _channelData[i].size = pixelTypeSize (c.channel().type) / pixelTypeSize (HALF); } const Box2i &dataWindow = hdr.dataWindow(); _minX = dataWindow.min.x; _maxX = dataWindow.max.x; _maxY = dataWindow.max.y; // // We can support uncompressed data in the machine's native // format only if all image channels are of type HALF. // assert (sizeof (unsigned short) == pixelTypeSize (HALF)); if (_numChans == numHalfChans) _format = NATIVE; } B44Compressor::~B44Compressor () { delete [] _tmpBuffer; delete [] _outBuffer; delete [] _channelData; } int B44Compressor::numScanLines () const { return _numScanLines; } Compressor::Format B44Compressor::format () const { return _format; } int B44Compressor::compress (const char *inPtr, int inSize, int minY, const char *&outPtr) { return compress (inPtr, inSize, Box2i (V2i (_minX, minY), V2i (_maxX, minY + numScanLines() - 1)), outPtr); } int B44Compressor::compressTile (const char *inPtr, int inSize, Imath::Box2i range, const char *&outPtr) { return compress (inPtr, inSize, range, outPtr); } int B44Compressor::uncompress (const char *inPtr, int inSize, int minY, const char *&outPtr) { return uncompress (inPtr, inSize, Box2i (V2i (_minX, minY), V2i (_maxX, minY + numScanLines() - 1)), outPtr); } int B44Compressor::uncompressTile (const char *inPtr, int inSize, Imath::Box2i range, const char *&outPtr) { return uncompress (inPtr, inSize, range, outPtr); } int B44Compressor::compress (const char *inPtr, int inSize, Imath::Box2i range, const char *&outPtr) { // // Compress a block of pixel data: First copy the input pixels // from the input buffer into _tmpBuffer, rearranging them such // that blocks of 4x4 pixels of a single channel can be accessed // conveniently. Then compress each 4x4 block of HALF pixel data // and append the result to the output buffer. Copy UINT and // FLOAT data to the output buffer without compressing them. // outPtr = _outBuffer; if (inSize == 0) { // // Special case - empty input buffer. // return 0; } // // For each channel, detemine how many pixels are stored // in the input buffer, and where those pixels will be // placed in _tmpBuffer. // int minX = range.min.x; int maxX = min (range.max.x, _maxX); int minY = range.min.y; int maxY = min (range.max.y, _maxY); unsigned short *tmpBufferEnd = _tmpBuffer; int i = 0; for (ChannelList::ConstIterator c = _channels.begin(); c != _channels.end(); ++c, ++i) { ChannelData &cd = _channelData[i]; cd.start = tmpBufferEnd; cd.end = cd.start; cd.nx = numSamples (c.channel().xSampling, minX, maxX); cd.ny = numSamples (c.channel().ySampling, minY, maxY); tmpBufferEnd += cd.nx * cd.ny * cd.size; } if (_format == XDR) { // // The data in the input buffer are in the machine-independent // Xdr format. Copy the HALF channels into _tmpBuffer and // convert them back into native format for compression. // Copy UINT and FLOAT channels verbatim into _tmpBuffer. // for (int y = minY; y <= maxY; ++y) { for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; if (modp (y, cd.ys) != 0) continue; if (cd.type == HALF) { for (int x = cd.nx; x > 0; --x) { Xdr::read (inPtr, *cd.end); ++cd.end; } } else { int n = cd.nx * cd.size; memcpy (cd.end, inPtr, n * sizeof (unsigned short)); inPtr += n * sizeof (unsigned short); cd.end += n; } } } } else { // // The input buffer contains only HALF channels, and they // are in native, machine-dependent format. Copy the pixels // into _tmpBuffer. // for (int y = minY; y <= maxY; ++y) { for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; #if defined (DEBUG) assert (cd.type == HALF); #endif if (modp (y, cd.ys) != 0) continue; int n = cd.nx * cd.size; memcpy (cd.end, inPtr, n * sizeof (unsigned short)); inPtr += n * sizeof (unsigned short); cd.end += n; } } } // // The pixels for each channel have been packed into a contiguous // block in _tmpBuffer. HALF channels are in native format; UINT // and FLOAT channels are in Xdr format. // #if defined (DEBUG) for (int i = 1; i < _numChans; ++i) assert (_channelData[i-1].end == _channelData[i].start); assert (_channelData[_numChans-1].end == tmpBufferEnd); #endif // // For each HALF channel, split the data in _tmpBuffer into 4x4 // pixel blocks. Compress each block and append the compressed // data to the output buffer. // // UINT and FLOAT channels are copied from _tmpBuffer into the // output buffer without further processing. // char *outEnd = _outBuffer; for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; if (cd.type != HALF) { // // UINT or FLOAT channel. // int n = cd.nx * cd.ny * cd.size * sizeof (unsigned short); memcpy (outEnd, cd.start, n); outEnd += n; continue; } // // HALF channel // for (int y = 0; y < cd.ny; y += 4) { // // Copy the next 4x4 pixel block into array s. // If the width, cd.nx, or the height, cd.ny, of // the pixel data in _tmpBuffer is not divisible // by 4, then pad the data by repeating the // rightmost column and the bottom row. // unsigned short *row0 = cd.start + y * cd.nx; unsigned short *row1 = row0 + cd.nx; unsigned short *row2 = row1 + cd.nx; unsigned short *row3 = row2 + cd.nx; if (y + 3 >= cd.ny) { if (y + 1 >= cd.ny) row1 = row0; if (y + 2 >= cd.ny) row2 = row1; row3 = row2; } for (int x = 0; x < cd.nx; x += 4) { unsigned short s[16]; if (x + 3 >= cd.nx) { int n = cd.nx - x; for (int i = 0; i < 4; ++i) { int j = min (i, n - 1); s[i + 0] = row0[j]; s[i + 4] = row1[j]; s[i + 8] = row2[j]; s[i + 12] = row3[j]; } } else { memcpy (&s[ 0], row0, 4 * sizeof (unsigned short)); memcpy (&s[ 4], row1, 4 * sizeof (unsigned short)); memcpy (&s[ 8], row2, 4 * sizeof (unsigned short)); memcpy (&s[12], row3, 4 * sizeof (unsigned short)); } row0 += 4; row1 += 4; row2 += 4; row3 += 4; // // Compress the contents of array s and append the // results to the output buffer. // if (cd.pLinear) convertFromLinear (s); outEnd += pack (s, (unsigned char *) outEnd, _optFlatFields, !cd.pLinear); } } } return outEnd - _outBuffer; } int B44Compressor::uncompress (const char *inPtr, int inSize, Imath::Box2i range, const char *&outPtr) { // // This function is the reverse of the compress() function, // above. First all pixels are moved from the input buffer // into _tmpBuffer. UINT and FLOAT channels are copied // verbatim; HALF channels are uncompressed in blocks of // 4x4 pixels. Then the pixels in _tmpBuffer are copied // into the output buffer and rearranged such that the data // for for each scan line form a contiguous block. // outPtr = _outBuffer; if (inSize == 0) { return 0; } int minX = range.min.x; int maxX = min (range.max.x, _maxX); int minY = range.min.y; int maxY = min (range.max.y, _maxY); unsigned short *tmpBufferEnd = _tmpBuffer; int i = 0; for (ChannelList::ConstIterator c = _channels.begin(); c != _channels.end(); ++c, ++i) { ChannelData &cd = _channelData[i]; cd.start = tmpBufferEnd; cd.end = cd.start; cd.nx = numSamples (c.channel().xSampling, minX, maxX); cd.ny = numSamples (c.channel().ySampling, minY, maxY); tmpBufferEnd += cd.nx * cd.ny * cd.size; } for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; if (cd.type != HALF) { // // UINT or FLOAT channel. // int n = cd.nx * cd.ny * cd.size * sizeof (unsigned short); if (inSize < n) notEnoughData(); memcpy (cd.start, inPtr, n); inPtr += n; inSize -= n; continue; } // // HALF channel // for (int y = 0; y < cd.ny; y += 4) { unsigned short *row0 = cd.start + y * cd.nx; unsigned short *row1 = row0 + cd.nx; unsigned short *row2 = row1 + cd.nx; unsigned short *row3 = row2 + cd.nx; for (int x = 0; x < cd.nx; x += 4) { unsigned short s[16]; if (inSize < 3) notEnoughData(); if (((const unsigned char *)inPtr)[2] == 0xfc) { unpack3 ((const unsigned char *)inPtr, s); inPtr += 3; inSize -= 3; } else { if (inSize < 14) notEnoughData(); unpack14 ((const unsigned char *)inPtr, s); inPtr += 14; inSize -= 14; } if (cd.pLinear) convertToLinear (s); int n = (x + 3 < cd.nx)? 4 * sizeof (unsigned short) : (cd.nx - x) * sizeof (unsigned short); if (y + 3 < cd.ny) { memcpy (row0, &s[ 0], n); memcpy (row1, &s[ 4], n); memcpy (row2, &s[ 8], n); memcpy (row3, &s[12], n); } else { memcpy (row0, &s[ 0], n); if (y + 1 < cd.ny) memcpy (row1, &s[ 4], n); if (y + 2 < cd.ny) memcpy (row2, &s[ 8], n); } row0 += 4; row1 += 4; row2 += 4; row3 += 4; } } } char *outEnd = _outBuffer; if (_format == XDR) { for (int y = minY; y <= maxY; ++y) { for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; if (modp (y, cd.ys) != 0) continue; if (cd.type == HALF) { for (int x = cd.nx; x > 0; --x) { Xdr::write (outEnd, *cd.end); ++cd.end; } } else { int n = cd.nx * cd.size; memcpy (outEnd, cd.end, n * sizeof (unsigned short)); outEnd += n * sizeof (unsigned short); cd.end += n; } } } } else { for (int y = minY; y <= maxY; ++y) { for (int i = 0; i < _numChans; ++i) { ChannelData &cd = _channelData[i]; #if defined (DEBUG) assert (cd.type == HALF); #endif if (modp (y, cd.ys) != 0) continue; int n = cd.nx * cd.size; memcpy (outEnd, cd.end, n * sizeof (unsigned short)); outEnd += n * sizeof (unsigned short); cd.end += n; } } } #if defined (DEBUG) for (int i = 1; i < _numChans; ++i) assert (_channelData[i-1].end == _channelData[i].start); assert (_channelData[_numChans-1].end == tmpBufferEnd); #endif if (inSize > 0) tooMuchData(); outPtr = _outBuffer; return outEnd - _outBuffer; } } // namespace Imf