text: ///////////////////////////////////////////////////////////////////////////
  //
  // 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 RgbaOutputFile
  //	class RgbaInputFile
  //
  //-----------------------------------------------------------------------------
  
  #include <ImfRgbaFile.h>
  #include <ImfOutputFile.h>
  #include <ImfInputFile.h>
  #include <ImfChannelList.h>
  #include <ImfRgbaYca.h>
  #include <ImfStandardAttributes.h>
  #include <ImathFun.h>
  #include <IlmThreadMutex.h>
  #include <Iex.h>
  #include <string.h>
  #include <algorithm>
  
  
  namespace Imf {
  
  using namespace std;
  using namespace Imath;
  using namespace RgbaYca;
  using namespace IlmThread;
  
  namespace {
  
  void
  insertChannels (Header &header, RgbaChannels rgbaChannels)
  {
      ChannelList ch;
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
      {
  	if (rgbaChannels & WRITE_Y)
  	{
  	    ch.insert ("Y", Channel (HALF, 1, 1));
  	}
  
  	if (rgbaChannels & WRITE_C)
  	{
  	    ch.insert ("RY", Channel (HALF, 2, 2, true));
  	    ch.insert ("BY", Channel (HALF, 2, 2, true));
  	}
      }
      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)
  {
      int i = 0;
  
      if (ch.findChannel ("R"))
  	i |= WRITE_R;
  
      if (ch.findChannel ("G"))
  	i |= WRITE_G;
      
      if (ch.findChannel ("B"))
  	i |= WRITE_B;
  
      if (ch.findChannel ("A"))
  	i |= WRITE_A;
  
      if (ch.findChannel ("Y"))
  	i |= WRITE_Y;
  
      if (ch.findChannel ("RY") || ch.findChannel ("BY"))
  	i |= WRITE_C;
  
      return RgbaChannels (i);
  }
  
  
  V3f
  ywFromHeader (const Header &header)
  {
      Chromaticities cr;
  
      if (hasChromaticities (header))
  	cr = chromaticities (header);
  
      return computeYw (cr);
  }
  
  } // namespace
  
  
  class RgbaOutputFile::ToYca: public Mutex
  {
    public:
  
       ToYca (OutputFile &outputFile, RgbaChannels rgbaChannels);
      ~ToYca ();
  
      void		setYCRounding (unsigned int roundY,
  	    			       unsigned int roundC);
  
      void		setFrameBuffer (const Rgba *base,
  					size_t xStride,
  					size_t yStride);
  
      void		writePixels (int numScanLines);
      int			currentScanLine () const;
  
    private:
  
      void		padTmpBuf ();
      void		rotateBuffers ();
      void		duplicateLastBuffer ();
      void		duplicateSecondToLastBuffer ();
      void		decimateChromaVertAndWriteScanLine ();
  
      OutputFile &	_outputFile;
      bool		_writeY;
      bool		_writeC;
      bool		_writeA;
      int			_xMin;
      int			_width;
      int			_height;
      int			_linesConverted;
      LineOrder		_lineOrder;
      int			_currentScanLine;
      V3f			_yw;
      Rgba *		_buf[N];
      Rgba *		_tmpBuf;
      const Rgba *	_fbBase;
      size_t		_fbXStride;
      size_t		_fbYStride;
      int			_roundY;
      int			_roundC;
  };
  
  
  RgbaOutputFile::ToYca::ToYca (OutputFile &outputFile,
  			      RgbaChannels rgbaChannels)
  :
      _outputFile (outputFile)
  {
      _writeY = (rgbaChannels & WRITE_Y)? true: false;
      _writeC = (rgbaChannels & WRITE_C)? true: false;
      _writeA = (rgbaChannels & WRITE_A)? true: false;
  
      const Box2i dw = _outputFile.header().dataWindow();
  
      _xMin = dw.min.x;
      _width  = dw.max.x - dw.min.x + 1;
      _height = dw.max.y - dw.min.y + 1;
  
      _linesConverted = 0;
      _lineOrder = _outputFile.header().lineOrder();
      
      if (_lineOrder == INCREASING_Y)
  	_currentScanLine = dw.min.y;
      else
  	_currentScanLine = dw.max.y;
  
      _yw = ywFromHeader (_outputFile.header());
  
      for (int i = 0; i < N; ++i)
  	_buf[i] = new Rgba[_width];
  
      _tmpBuf = new Rgba[_width + N - 1];
  
      _fbBase = 0;
      _fbXStride = 0;
      _fbYStride = 0;
  
      _roundY = 7;
      _roundC = 5;
  }
  
  
  RgbaOutputFile::ToYca::~ToYca ()
  {
      for (int i = 0; i < N; ++i)
  	delete [] _buf[i];
  
      delete [] _tmpBuf;
  }
  
  
  void
  RgbaOutputFile::ToYca::setYCRounding (unsigned int roundY,
  				      unsigned int roundC)
  {
      _roundY = roundY;
      _roundC = roundC;
  }
  
  
  void
  RgbaOutputFile::ToYca::setFrameBuffer (const Rgba *base,
  				       size_t xStride,
  				       size_t yStride)
  {
      if (_fbBase == 0)
      {
  	FrameBuffer fb;
  
  	if (_writeY)
  	{
  	    fb.insert ("Y",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[-_xMin].g,	// base
  			      sizeof (Rgba),			// xStride
  			      0,				// yStride
  			      1,				// xSampling
  			      1));				// ySampling
  	}
  
  	if (_writeC)
  	{
  	    fb.insert ("RY",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[-_xMin].r,	// base
  			      sizeof (Rgba) * 2,		// xStride
  			      0,				// yStride
  			      2,				// xSampling
  			      2));				// ySampling
  
  	    fb.insert ("BY",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[-_xMin].b,	// base
  			      sizeof (Rgba) * 2,		// xStride
  			      0,				// yStride
  			      2,				// xSampling
  			      2));				// ySampling
  	}
  
  	if (_writeA)
  	{
  	    fb.insert ("A",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[-_xMin].a,	// base
  			      sizeof (Rgba),			// xStride
  			      0,				// yStride
  			      1,				// xSampling
  			      1));				// ySampling
  	}
  
  	_outputFile.setFrameBuffer (fb);
      }
  
      _fbBase = base;
      _fbXStride = xStride;
      _fbYStride = yStride;
  }
  
  
  void
  RgbaOutputFile::ToYca::writePixels (int numScanLines)
  {
      if (_fbBase == 0)
      {
  	THROW (Iex::ArgExc, "No frame buffer was specified as the "
  			    "pixel data source for image file "
  			    "\"" << _outputFile.fileName() << "\".");
      }
  
      if (_writeY && !_writeC)
      {
  	//
  	// We are writing only luminance; filtering
  	// and subsampling are not necessary.
  	//
  
  	for (int i = 0; i < numScanLines; ++i)
  	{
  	    //
  	    // Copy the next scan line from the caller's
  	    // frame buffer into _tmpBuf.
  	    //
  
  	    for (int j = 0; j < _width; ++j)
  	    {
  		_tmpBuf[j] = _fbBase[_fbYStride * _currentScanLine +
  				     _fbXStride * (j + _xMin)];
  	    }
  
  	    //
  	    // Convert the scan line from RGB to luminance/chroma,
  	    // and store the result in the output file.
  	    //
  
  	    RGBAtoYCA (_yw, _width, _writeA, _tmpBuf, _tmpBuf);
  	    _outputFile.writePixels (1);
  
  	    ++_linesConverted;
  
  	    if (_lineOrder == INCREASING_Y)
  		++_currentScanLine;
  	    else
  		--_currentScanLine;
  	}
      }
      else
      {
  	//
  	// We are writing chroma; the pixels must be filtered and subsampled.
  	//
  
  	for (int i = 0; i < numScanLines; ++i)
  	{
  	    //
  	    // Copy the next scan line from the caller's
  	    // frame buffer into _tmpBuf.
  	    //
  
  	    for (int j = 0; j < _width; ++j)
  	    {
  		_tmpBuf[j + N2] = _fbBase[_fbYStride * _currentScanLine +
  					  _fbXStride * (j + _xMin)];
  	    }
  
  	    //
  	    // Convert the scan line from RGB to luminance/chroma.
  	    //
  
  	    RGBAtoYCA (_yw, _width, _writeA, _tmpBuf + N2, _tmpBuf + N2);
  
  	    //
  	    // Append N2 copies of the first and last pixel to the
  	    // beginning and end of the scan line.
  	    //
  
  	    padTmpBuf ();
  
  	    //
  	    // Filter and subsample the scan line's chroma channels
  	    // horizontally; store the result in _buf.
  	    //
  
  	    rotateBuffers();
  	    decimateChromaHoriz (_width, _tmpBuf, _buf[N - 1]);
  
  	    //
  	    // If this is the first scan line in the image,
  	    // store N2 more copies of the scan line in _buf.
  	    //
  
  	    if (_linesConverted == 0)
  	    {
  		for (int j = 0; j < N2; ++j)
  		    duplicateLastBuffer();
  	    }
  
  	    ++_linesConverted;
  
  	    //
  	    // If we have have converted at least N2 scan lines from
  	    // RGBA to luminance/chroma, then we can start to filter
  	    // and subsample vertically, and store pixels in the
  	    // output file.
  	    //
  
  	    if (_linesConverted > N2)
  		decimateChromaVertAndWriteScanLine();
  
  	    //
  	    // If we have already converted the last scan line in
  	    // the image to luminance/chroma, filter, subsample and
  	    // store the remaining scan lines in _buf.
  	    //
  
  	    if (_linesConverted >= _height)
  	    {
  		for (int j = 0; j < N2 - _height; ++j)
  		    duplicateLastBuffer();
  
  		duplicateSecondToLastBuffer();
  		++_linesConverted;
  		decimateChromaVertAndWriteScanLine();
  
  		for (int j = 1; j < min (_height, N2); ++j)
  		{
  		    duplicateLastBuffer();
  		    ++_linesConverted;
  		    decimateChromaVertAndWriteScanLine();
  		}
  	    }
  
  	    if (_lineOrder == INCREASING_Y)
  		++_currentScanLine;
  	    else
  		--_currentScanLine;
  	}
      }
  }
  
  
  int
  RgbaOutputFile::ToYca::currentScanLine () const
  {
      return _currentScanLine;
  }
  
  
  void
  RgbaOutputFile::ToYca::padTmpBuf ()
  {
      for (int i = 0; i < N2; ++i)
      {
  	_tmpBuf[i] = _tmpBuf[N2];
  	_tmpBuf[_width + N2 + i] = _tmpBuf[_width + N2 - 2];
      }
  }
  
  
  void
  RgbaOutputFile::ToYca::rotateBuffers ()
  {
      Rgba *tmp = _buf[0];
  
      for (int i = 0; i < N - 1; ++i)
  	_buf[i] = _buf[i + 1];
  
      _buf[N - 1] = tmp;
  }
  
  
  void
  RgbaOutputFile::ToYca::duplicateLastBuffer ()
  {
      rotateBuffers();
      memcpy (_buf[N - 1], _buf[N - 2], _width * sizeof (Rgba));
  }
  
  
  void
  RgbaOutputFile::ToYca::duplicateSecondToLastBuffer ()
  {
      rotateBuffers();
      memcpy (_buf[N - 1], _buf[N - 3], _width * sizeof (Rgba));
  }
  
  
  void
  RgbaOutputFile::ToYca::decimateChromaVertAndWriteScanLine ()
  {
      if (_linesConverted & 1)
  	memcpy (_tmpBuf, _buf[N2], _width * sizeof (Rgba));
      else
  	decimateChromaVert (_width, _buf, _tmpBuf);
  
      if (_writeY && _writeC)
  	roundYCA (_width, _roundY, _roundC, _tmpBuf, _tmpBuf);
  
      _outputFile.writePixels (1);
  }
  
  
  RgbaOutputFile::RgbaOutputFile (const char name[],
  				const Header &header,
  				RgbaChannels rgbaChannels,
                                  int numThreads):
      _outputFile (0),
      _toYca (0)
  {
      Header hd (header);
      insertChannels (hd, rgbaChannels);
      _outputFile = new OutputFile (name, hd, numThreads);
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_toYca = new ToYca (*_outputFile, rgbaChannels);
  }
  
  
  RgbaOutputFile::RgbaOutputFile (OStream &os,
  				const Header &header,
  				RgbaChannels rgbaChannels,
                                  int numThreads):
      _outputFile (0),
      _toYca (0)
  {
      Header hd (header);
      insertChannels (hd, rgbaChannels);
      _outputFile = new OutputFile (os, hd, numThreads);
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_toYca = new ToYca (*_outputFile, rgbaChannels);
  }
  
  
  RgbaOutputFile::RgbaOutputFile (const char name[],
  				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),
      _toYca (0)
  {
      Header hd (displayWindow,
  	       dataWindow.isEmpty()? displayWindow: dataWindow,
  	       pixelAspectRatio,
  	       screenWindowCenter,
  	       screenWindowWidth,
  	       lineOrder,
  	       compression);
  
      insertChannels (hd, rgbaChannels);
      _outputFile = new OutputFile (name, hd, numThreads);
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_toYca = new ToYca (*_outputFile, rgbaChannels);
  }
  
  
  RgbaOutputFile::RgbaOutputFile (const char name[],
  				int width,
  				int height,
  				RgbaChannels rgbaChannels,
  				float pixelAspectRatio,
  				const Imath::V2f screenWindowCenter,
  				float screenWindowWidth,
  				LineOrder lineOrder,
  				Compression compression,
                                  int numThreads):
      _outputFile (0),
      _toYca (0)
  {
      Header hd (width,
  	       height,
  	       pixelAspectRatio,
  	       screenWindowCenter,
  	       screenWindowWidth,
  	       lineOrder,
  	       compression);
  
      insertChannels (hd, rgbaChannels);
      _outputFile = new OutputFile (name, hd, numThreads);
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_toYca = new ToYca (*_outputFile, rgbaChannels);
  }
  
  
  RgbaOutputFile::~RgbaOutputFile ()
  {
      delete _toYca;
      delete _outputFile;
  }
  
  
  void
  RgbaOutputFile::setFrameBuffer (const Rgba *base,
  				size_t xStride,
  				size_t yStride)
  {
      if (_toYca)
      {
  	Lock lock (*_toYca);
  	_toYca->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);
      }
  }
  
  
  void	
  RgbaOutputFile::writePixels (int numScanLines)
  {
      if (_toYca)
      {
  	Lock lock (*_toYca);
  	_toYca->writePixels (numScanLines);
      }
      else
      {
  	_outputFile->writePixels (numScanLines);
      }
  }
  
  
  int	
  RgbaOutputFile::currentScanLine () const
  {
      if (_toYca)
      {
  	Lock lock (*_toYca);
  	return _toYca->currentScanLine();
      }
      else
      {
  	return _outputFile->currentScanLine();
      }
  }
  
  
  const Header &
  RgbaOutputFile::header () const
  {
      return _outputFile->header();
  }
  
  
  const FrameBuffer &
  RgbaOutputFile::frameBuffer () const
  {
      return _outputFile->frameBuffer();
  }
  
  
  const Imath::Box2i &
  RgbaOutputFile::displayWindow () const
  {
      return _outputFile->header().displayWindow();
  }
  
  
  const Imath::Box2i &
  RgbaOutputFile::dataWindow () const
  {
      return _outputFile->header().dataWindow();
  }
  
  
  float	
  RgbaOutputFile::pixelAspectRatio () const
  {
      return _outputFile->header().pixelAspectRatio();
  }
  
  
  const Imath::V2f
  RgbaOutputFile::screenWindowCenter () const
  {
      return _outputFile->header().screenWindowCenter();
  }
  
  
  float	
  RgbaOutputFile::screenWindowWidth () const
  {
      return _outputFile->header().screenWindowWidth();
  }
  
  
  LineOrder
  RgbaOutputFile::lineOrder () const
  {
      return _outputFile->header().lineOrder();
  }
  
  
  Compression
  RgbaOutputFile::compression () const
  {
      return _outputFile->header().compression();
  }
  
  
  RgbaChannels
  RgbaOutputFile::channels () const
  {
      return rgbaChannels (_outputFile->header().channels());
  }
  
  
  void		
  RgbaOutputFile::updatePreviewImage (const PreviewRgba newPixels[])
  {
      _outputFile->updatePreviewImage (newPixels);
  }
  
  
  void		
  RgbaOutputFile::setYCRounding (unsigned int roundY, unsigned int roundC)
  {
      if (_toYca)
      {
  	Lock lock (*_toYca);
  	_toYca->setYCRounding (roundY, roundC);
      }
  }
  
  
  void	
  RgbaOutputFile::breakScanLine  (int y, int offset, int length, char c)
  {
      _outputFile->breakScanLine (y, offset, length, c);
  }
  
  
  class RgbaInputFile::FromYca: public Mutex
  {
    public:
  
       FromYca (InputFile &inputFile, RgbaChannels rgbaChannels);
      ~FromYca ();
  
      void		setFrameBuffer (Rgba *base,
  					size_t xStride,
  					size_t yStride);
  
      void		readPixels (int scanLine1, int scanLine2);
  
    private:
  
      void		readPixels (int scanLine);
      void		rotateBuf1 (int d);
      void		rotateBuf2 (int d);
      void		readYCAScanLine (int y, Rgba buf[]);
      void		padTmpBuf ();
  
      InputFile &		_inputFile;
      bool		_readC;
      int			_xMin;
      int			_yMin;
      int 		_yMax;
      int			_width;
      int			_height;
      int			_currentScanLine;
      LineOrder		_lineOrder;
      V3f			_yw;
      Rgba *		_buf1[N + 2];
      Rgba *		_buf2[3];
      Rgba *		_tmpBuf;
      Rgba *		_fbBase;
      size_t		_fbXStride;
      size_t		_fbYStride;
  };
  
  
  RgbaInputFile::FromYca::FromYca (InputFile &inputFile,
  				 RgbaChannels rgbaChannels)
  :
      _inputFile (inputFile)
  {
      _readC = (rgbaChannels & WRITE_C)? true: false;
  
      const Box2i dw = _inputFile.header().dataWindow();
  
      _xMin = dw.min.x;
      _yMin = dw.min.y;
      _yMax = dw.max.y;
      _width  = dw.max.x - dw.min.x + 1;
      _height = dw.max.y - dw.min.y + 1;
      _currentScanLine = dw.min.y - N - 2;
      _lineOrder = _inputFile.header().lineOrder();
      _yw = ywFromHeader (_inputFile.header());
  
      for (int i = 0; i < N + 2; ++i)
  	_buf1[i] = new Rgba[_width];
      
      for (int i = 0; i < 3; ++i)
  	_buf2[i] = new Rgba[_width];
  
      _tmpBuf = new Rgba[_width + N - 1];
  
      _fbBase = 0;
      _fbXStride = 0;
      _fbYStride = 0;
  }
  
  
  RgbaInputFile::FromYca::~FromYca ()
  {
      for (int i = 0; i < N + 2; ++i)
  	delete [] _buf1[i];
  
      for (int i = 0; i < 3; ++i)
  	delete [] _buf2[i];
  
      delete [] _tmpBuf;
  }
  
  
  void
  RgbaInputFile::FromYca::setFrameBuffer (Rgba *base,
  					size_t xStride,
  					size_t yStride)
  {
      if (_fbBase == 0)
      {
  	FrameBuffer fb;
  
  	fb.insert ("Y",
  		   Slice (HALF,					// type
  			  (char *) &_tmpBuf[N2 - _xMin].g,	// base
  			  sizeof (Rgba),			// xStride
  			  0,					// yStride
  			  1,					// xSampling
  			  1,					// ySampling
  			  0.5));				// fillValue
  
  	if (_readC)
  	{
  	    fb.insert ("RY",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[N2 - _xMin].r,	// base
  			      sizeof (Rgba) * 2,		// xStride
  			      0,				// yStride
  			      2,				// xSampling
  			      2,				// ySampling
  			      0.0));				// fillValue
  
  	    fb.insert ("BY",
  		       Slice (HALF,				// type
  			      (char *) &_tmpBuf[N2 - _xMin].b,	// base
  			      sizeof (Rgba) * 2,		// xStride
  			      0,				// yStride
  			      2,				// xSampling
  			      2,				// ySampling
  			      0.0));				// fillValue
  	}
  
  	fb.insert ("A",
  		   Slice (HALF,					// type
  			  (char *) &_tmpBuf[N2 - _xMin].a,	// base
  			  sizeof (Rgba),			// xStride
  			  0,					// yStride
  			  1,					// xSampling
  			  1,					// ySampling
  			  1.0));				// fillValue
  
  	_inputFile.setFrameBuffer (fb);
      }
  
      _fbBase = base;
      _fbXStride = xStride;
      _fbYStride = yStride;
  }
  
  
  void	
  RgbaInputFile::FromYca::readPixels (int scanLine1, int scanLine2)
  {
      int minY = min (scanLine1, scanLine2);
      int maxY = max (scanLine1, scanLine2);
  
      if (_lineOrder == INCREASING_Y)
      {
  	for (int y = minY; y <= maxY; ++y)
  	    readPixels (y);
      }
      else
      {
  	for (int y = maxY; y >= minY; --y)
  	    readPixels (y);
      }
  }
  
  
  void	
  RgbaInputFile::FromYca::readPixels (int scanLine)
  {
      if (_fbBase == 0)
      {
  	THROW (Iex::ArgExc, "No frame buffer was specified as the "
  			    "pixel data destination for image file "
  			    "\"" << _inputFile.fileName() << "\".");
      }
  
      //
      // In order to convert one scan line to RGB format, we need that
      // scan line plus N2+1 extra scan lines above and N2+1 scan lines
      // below in luminance/chroma format.
      //
      // We allow random access to scan lines, but we buffer partially
      // processed luminance/chroma data in order to make reading pixels
      // in increasing y or decreasing y order reasonably efficient:
      //
      //	_currentScanLine	holds the y coordinate of the scan line
      //				that was most recently read.
      //
      //	_buf1			contains scan lines _currentScanLine-N2-1
      //				through _currentScanLine+N2+1 in
      //				luminance/chroma format.  Odd-numbered
      //				lines contain no chroma data.  Even-numbered
      //				lines have valid chroma data for all pixels.
      //
      //  _buf2			contains scan lines _currentScanLine-1
      //  			through _currentScanLine+1, in RGB format.
      //				Super-saturated pixels (see ImfRgbaYca.h)
      //				have not yet been eliminated.
      //
      // If the scan line we are trying to read now is close enough to
      // _currentScanLine, we don't have to recompute the contents of _buf1
      // and _buf2 from scratch.  We can rotate _buf1 and _buf2, and fill
      // in the missing data.
      //
  
      int dy = scanLine - _currentScanLine;
  
      if (abs (dy) < N + 2)
  	rotateBuf1 (dy);
  
      if (abs (dy) < 3)
  	rotateBuf2 (dy);
  
      if (dy < 0)
      {
  	{
  	    int n = min (-dy, N + 2);
  	    int yMin = scanLine - N2 - 1;
  
  	    for (int i = n - 1; i >= 0; --i)
  		readYCAScanLine (yMin + i, _buf1[i]);
  	}
  
  	{
  	    int n = min (-dy, 3);
  
  	    for (int i = 0; i < n; ++i)
  	    {
  		if ((scanLine + i) & 1)
  		{
  		    YCAtoRGBA (_yw, _width, _buf1[N2 + i], _buf2[i]);
  		}
  		else
  		{
  		    reconstructChromaVert (_width, _buf1 + i, _buf2[i]);
  		    YCAtoRGBA (_yw, _width, _buf2[i], _buf2[i]);
  		}
  	    }
  	}
      }
      else
      {
  	{
  	    int n = min (dy, N + 2);
  	    int yMax = scanLine + N2 + 1;
  
  	    for (int i = n - 1; i >= 0; --i)
  		readYCAScanLine (yMax - i, _buf1[N + 1 - i]);
  	}
  
  	{
  	    int n = min (dy, 3);
  
  	    for (int i = 2; i > 2 - n; --i)
  	    {
  		if ((scanLine + i) & 1)
  		{
  		    YCAtoRGBA (_yw, _width, _buf1[N2 + i], _buf2[i]);
  		}
  		else
  		{
  		    reconstructChromaVert (_width, _buf1 + i, _buf2[i]);
  		    YCAtoRGBA (_yw, _width, _buf2[i], _buf2[i]);
  		}
  	    }
  	}
      }
  
      fixSaturation (_yw, _width, _buf2, _tmpBuf);
  
      for (int i = 0; i < _width; ++i)
  	_fbBase[_fbYStride * scanLine + _fbXStride * (i + _xMin)] = _tmpBuf[i];
  
      _currentScanLine = scanLine;
  }
  
  
  void
  RgbaInputFile::FromYca::rotateBuf1 (int d)
  {
      d = modp (d, N + 2);
  
      Rgba *tmp[N + 2];
  
      for (int i = 0; i < N + 2; ++i)
  	tmp[i] = _buf1[i];
  
      for (int i = 0; i < N + 2; ++i)
  	_buf1[i] = tmp[(i + d) % (N + 2)];
  }
  
  
  void
  RgbaInputFile::FromYca::rotateBuf2 (int d)
  {
      d = modp (d, 3);
  
      Rgba *tmp[3];
  
      for (int i = 0; i < 3; ++i)
  	tmp[i] = _buf2[i];
  
      for (int i = 0; i < 3; ++i)
  	_buf2[i] = tmp[(i + d) % 3];
  }
  
  
  void
  RgbaInputFile::FromYca::readYCAScanLine (int y, Rgba *buf)
  {
      //
      // Clamp y.
      //
  
      if (y < _yMin)
  	y = _yMin;
      else if (y > _yMax)
  	y = _yMax - 1;
  
      //
      // Read scan line y into _tmpBuf.
      //
  
      _inputFile.readPixels (y);
  
      //
      // Reconstruct missing chroma samples and copy
      // the scan line into buf.
      //
  
      if (!_readC)
      {
  	for (int i = 0; i < _width; ++i)
  	{
  	    _tmpBuf[i + N2].r = 0;
  	    _tmpBuf[i + N2].b = 0;
  	}
      }
  
      if (y & 1)
      {
  	memcpy (buf, _tmpBuf + N2, _width * sizeof (Rgba));
      }
      else
      {
  	padTmpBuf();
  	reconstructChromaHoriz (_width, _tmpBuf, buf);
      }
  }
  
  
  void
  RgbaInputFile::FromYca::padTmpBuf ()
  {
      for (int i = 0; i < N2; ++i)
      {
  	_tmpBuf[i] = _tmpBuf[N2];
  	_tmpBuf[_width + N2 + i] = _tmpBuf[_width + N2 - 2];
      }
  }
  
  
  RgbaInputFile::RgbaInputFile (const char name[], int numThreads):
      _inputFile (new InputFile (name, numThreads)),
      _fromYca (0)
  {
      RgbaChannels rgbaChannels = channels();
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_fromYca = new FromYca (*_inputFile, rgbaChannels);
  }
  
  
  RgbaInputFile::RgbaInputFile (IStream &is, int numThreads):
      _inputFile (new InputFile (is, numThreads)),
      _fromYca (0)
  {
      RgbaChannels rgbaChannels = channels();
  
      if (rgbaChannels & (WRITE_Y | WRITE_C))
  	_fromYca = new FromYca (*_inputFile, rgbaChannels);
  }
  
  
  RgbaInputFile::~RgbaInputFile ()
  {
      delete _inputFile;
      delete _fromYca;
  }
  
  
  void	
  RgbaInputFile::setFrameBuffer (Rgba *base, size_t xStride, size_t yStride)
  {
      if (_fromYca)
      {
  	Lock lock (*_fromYca);
  	_fromYca->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,
  			       1, 1,	// xSampling, ySampling
  			       0.0));	// fillValue
  
  	fb.insert ("G", Slice (HALF,
  			       (char *) &base[0].g,
  			       xs, ys,
  			       1, 1,	// xSampling, ySampling
  			       0.0));	// fillValue
  
  	fb.insert ("B", Slice (HALF,
  			       (char *) &base[0].b,
  			       xs, ys,
  			       1, 1,	// xSampling, ySampling
  			       0.0));	// fillValue
  
  	fb.insert ("A", Slice (HALF,
  			       (char *) &base[0].a,
  			       xs, ys,
  			       1, 1,	// xSampling, ySampling
  			       1.0));	// fillValue
  
  	_inputFile->setFrameBuffer (fb);
      }
  }
  
  
  void	
  RgbaInputFile::readPixels (int scanLine1, int scanLine2)
  {
      if (_fromYca)
      {
  	Lock lock (*_fromYca);
  	_fromYca->readPixels (scanLine1, scanLine2);
      }
      else
      {
  	_inputFile->readPixels (scanLine1, scanLine2);
      }
  }
  
  
  void	
  RgbaInputFile::readPixels (int scanLine)
  {
      readPixels (scanLine, scanLine);
  }
  
  
  bool
  RgbaInputFile::isComplete () const
  {
      return _inputFile->isComplete();
  }
  
  
  const Header &
  RgbaInputFile::header () const
  {
      return _inputFile->header();
  }
  
  
  const char *
  RgbaInputFile::fileName () const
  {
      return _inputFile->fileName();
  }
  
  
  const FrameBuffer &	
  RgbaInputFile::frameBuffer () const
  {
      return _inputFile->frameBuffer();
  }
  
  
  const Imath::Box2i &
  RgbaInputFile::displayWindow () const
  {
      return _inputFile->header().displayWindow();
  }
  
  
  const Imath::Box2i &
  RgbaInputFile::dataWindow () const
  {
      return _inputFile->header().dataWindow();
  }
  
  
  float	
  RgbaInputFile::pixelAspectRatio () const
  {
      return _inputFile->header().pixelAspectRatio();
  }
  
  
  const Imath::V2f	
  RgbaInputFile::screenWindowCenter () const
  {
      return _inputFile->header().screenWindowCenter();
  }
  
  
  float	
  RgbaInputFile::screenWindowWidth () const
  {
      return _inputFile->header().screenWindowWidth();
  }
  
  
  LineOrder
  RgbaInputFile::lineOrder () const
  {
      return _inputFile->header().lineOrder();
  }
  
  
  Compression
  RgbaInputFile::compression () const
  {
      return _inputFile->header().compression();
  }
  
  
  RgbaChannels	
  RgbaInputFile::channels () const
  {
      return rgbaChannels (_inputFile->header().channels());
  }
  
  
  int
  RgbaInputFile::version () const
  {
      return _inputFile->version();
  }
  
  
  } // namespace Imf
  