/*****************************************************************************/
// Copyright 2006-2012 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_read_image.cpp#7 $ */ 
/* $DateTime: 2012/07/31 22:04:34 $ */
/* $Change: 840853 $ */
/* $Author: tknoll $ */

/*****************************************************************************/

#include "dng_read_image.h"

#include "dng_abort_sniffer.h"
#include "dng_area_task.h"
#include "dng_bottlenecks.h"
#include "dng_exceptions.h"
#include "dng_flags.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_ifd.h"
#include "dng_jpeg_image.h"
#include "dng_lossless_jpeg.h"
#include "dng_mutex.h"
#include "dng_memory.h"
#include "dng_pixel_buffer.h"
#include "dng_safe_arithmetic.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"

#include "zlib.h"

#if qDNGUseLibJPEG
#include "dng_jpeg_memory_source.h"
#include "dng_jpeglib.h"
#endif

#include <limits>

/******************************************************************************/

static void DecodeDelta8 (uint8 *dPtr,
						  uint32 rows,
						  uint32 cols,
						  uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = 1; col < cols; col++)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

	}

/******************************************************************************/

static void DecodeDelta16 (uint16 *dPtr,
						   uint32 rows,
						   uint32 cols,
						   uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = 1; col < cols; col++)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

	}
	
/******************************************************************************/

static void DecodeDelta32 (uint32 *dPtr,
						   uint32 rows,
						   uint32 cols,
						   uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = 1; col < cols; col++)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

	}
	
/*****************************************************************************/

inline void DecodeDeltaBytes (uint8 *bytePtr, int32 cols, int32 channels)
	{
	
	if (channels == 1)
		{
		
		uint8 b0 = bytePtr [0];
		
		bytePtr += 1;
		
		for (int32 col = 1; col < cols; ++col)
			{
			
			b0 += bytePtr [0];
			
			bytePtr [0] = b0;
			
			bytePtr += 1;

			}
			
		}
	
	else if (channels == 3)
		{
		
		uint8 b0 = bytePtr [0];
		uint8 b1 = bytePtr [1];
		uint8 b2 = bytePtr [2];
		
		bytePtr += 3;
		
		for (int32 col = 1; col < cols; ++col)
			{
			
			b0 += bytePtr [0];
			b1 += bytePtr [1];
			b2 += bytePtr [2];
			
			bytePtr [0] = b0;
			bytePtr [1] = b1;
			bytePtr [2] = b2;
			
			bytePtr += 3;

			}
			
		}
		
	else if (channels == 4)
		{
		
		uint8 b0 = bytePtr [0];
		uint8 b1 = bytePtr [1];
		uint8 b2 = bytePtr [2];
		uint8 b3 = bytePtr [3];
		
		bytePtr += 4;
		
		for (int32 col = 1; col < cols; ++col)
			{
			
			b0 += bytePtr [0];
			b1 += bytePtr [1];
			b2 += bytePtr [2];
			b3 += bytePtr [3];
			
			bytePtr [0] = b0;
			bytePtr [1] = b1;
			bytePtr [2] = b2;
			bytePtr [3] = b3;
			
			bytePtr += 4;

			}
			
		}
		
	else
		{
		
		for (int32 col = 1; col < cols; ++col)
			{
			
			for (int32 chan = 0; chan < channels; ++chan)
				{
				
				bytePtr [chan + channels] += bytePtr [chan];
				
				}
				
			bytePtr += channels;

			}
			
		}

	}
						    
/*****************************************************************************/

static void DecodeFPDelta (uint8 *input,
						   uint8 *output,
						   int32 cols,
						   int32 channels,
						   int32 bytesPerSample)
	{
	
	DecodeDeltaBytes (input, cols * bytesPerSample, channels);
	
	int32 rowIncrement = cols * channels;
	
	if (bytesPerSample == 2)
		{
		
		#if qDNGBigEndian
		const uint8 *input0 = input;
		const uint8 *input1 = input + rowIncrement;
		#else
		const uint8 *input1 = input;
		const uint8 *input0 = input + rowIncrement;
		#endif
		
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			output [0] = input0 [col];
			output [1] = input1 [col];
			
			output += 2;
				
			}
			
		}
		
	else if (bytesPerSample == 3)
		{
		
		const uint8 *input0 = input;
		const uint8 *input1 = input + rowIncrement;
		const uint8 *input2 = input + rowIncrement * 2;
		
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			output [0] = input0 [col];
			output [1] = input1 [col];
			output [2] = input2 [col];
			
			output += 3;
				
			}
			
		}
		
	else
		{
		
		#if qDNGBigEndian
		const uint8 *input0 = input;
		const uint8 *input1 = input + rowIncrement;
		const uint8 *input2 = input + rowIncrement * 2;
		const uint8 *input3 = input + rowIncrement * 3;
		#else
		const uint8 *input3 = input;
		const uint8 *input2 = input + rowIncrement;
		const uint8 *input1 = input + rowIncrement * 2;
		const uint8 *input0 = input + rowIncrement * 3;
		#endif
		
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			output [0] = input0 [col];
			output [1] = input1 [col];
			output [2] = input2 [col];
			output [3] = input3 [col];
			
			output += 4;
				
			}
			
		}
		
	}	
					    
/*****************************************************************************/

bool DecodePackBits (dng_stream &stream,
					 uint8 *dPtr,
					 int32 dstCount)
	{

	while (dstCount > 0)
		{
		
		int32 runCount = (int8) stream.Get_uint8 ();

		if (runCount >= 0)
			{
			
			++runCount;
			
			dstCount -= runCount;

			if (dstCount < 0)
				return false;
				
			stream.Get (dPtr, runCount);
			
			dPtr += runCount;
			
			}
			
		else
			{
			
			runCount = -runCount + 1;

			dstCount -= runCount;

			if (dstCount < 0)
				return false;
				
			uint8 x = stream.Get_uint8 ();
			
			while (runCount--)
				{
				
				*(dPtr++) = x;
				
				}

			}
			
		}

	return true;

	}

/******************************************************************************/

class dng_lzw_expander
	{
	
	private:
	
		enum
			{
			kResetCode = 256,
			kEndCode   = 257,
			kTableSize = 4096
			};
		
		struct LZWExpanderNode
			{
			int16 prefix;
			int16 final;
			int16 depth;
			int16 fake_for_padding;
			};

		dng_memory_data fBuffer;

		LZWExpanderNode *fTable;
		
		const uint8 *fSrcPtr;
		
		int32 fSrcCount;
		
		int32 fByteOffset;

		uint32 fBitBuffer;
		int32 fBitBufferCount;
		
		int32 fNextCode;
		
		int32 fCodeSize;
		
	public:
	
		dng_lzw_expander ();

		bool Expand (const uint8 *sPtr,
					 uint8 *dPtr,
					 int32 sCount,
					 int32 dCount);
	
	private:
	
		void InitTable ();
	
		void AddTable (int32 w, int32 k);
		
		bool GetCodeWord (int32 &code);

		// Hidden copy constructor and assignment operator.
	
		dng_lzw_expander (const dng_lzw_expander &expander);
		
		dng_lzw_expander & operator= (const dng_lzw_expander &expander);

	};

/******************************************************************************/

dng_lzw_expander::dng_lzw_expander ()

	:	fBuffer			 ()
	,	fTable			 (NULL)
	,	fSrcPtr			 (NULL)
	,	fSrcCount		 (0)
	,	fByteOffset		 (0)
	,	fBitBuffer		 (0)
	,	fBitBufferCount  (0)
	,	fNextCode		 (0)
	,	fCodeSize		 (0)
	
	{
	
	fBuffer.Allocate (kTableSize * sizeof (LZWExpanderNode));
	
	fTable = (LZWExpanderNode *) fBuffer.Buffer ();

	}

/******************************************************************************/

void dng_lzw_expander::InitTable ()
	{

	fCodeSize = 9;

	fNextCode = 258;
		
	LZWExpanderNode *node = &fTable [0];
	
	for (int32 code = 0; code < 256; code++)
		{
		
		node->prefix  = -1;
		node->final   = (int16) code;
		node->depth   = 1;
		
		node++;
		
		}
		
	}

/******************************************************************************/

void dng_lzw_expander::AddTable (int32 w, int32 k)
	{
	
	DNG_ASSERT ((w >= 0) && (w <= kTableSize),
				"bad w value in dng_lzw_expander::AddTable");

	LZWExpanderNode *parentNode = &fTable [w];
	
	int32 nextCode = fNextCode;
	
	fNextCode++;
	
	DNG_ASSERT ((nextCode >= 0) && (nextCode <= kTableSize),
				"bad fNextCode value in dng_lzw_expander::AddTable");
	
	LZWExpanderNode *node = &fTable [nextCode];
	
	node->prefix  = (int16) w;
	node->final   = (int16) k;
	node->depth   = 1 + parentNode->depth;
	
	if (nextCode + 1 == (1 << fCodeSize) - 1)
		{
		if (fCodeSize != 12)
			fCodeSize++;
		}
		
	}

/******************************************************************************/

bool dng_lzw_expander::GetCodeWord (int32 &code)
	{

	// The bit buffer has the current code in the most significant bits,
	// so shift off the low orders.
	
	int32 codeSize = fCodeSize;

	code = fBitBuffer >> (32 - codeSize);

	if (fBitBufferCount >= codeSize)
		{
		
		// Typical case; get the code from the bit buffer.

		fBitBuffer     <<= codeSize;
		fBitBufferCount -= codeSize;
		
		}

	else
		{
		
		// The buffer needs to be refreshed.

		const int32 bitsSoFar = fBitBufferCount;

		if (fByteOffset >= fSrcCount)
			return false;

		// Buffer a long word
		
		const uint8 *ptr = fSrcPtr + fByteOffset;

		#if qDNGBigEndian

		fBitBuffer = *((const uint32 *) ptr);

		#else
		
			{
			
			uint32 b0 = ptr [0];
			uint32 b1 = ptr [1];
			uint32 b2 = ptr [2];
			uint32 b3 = ptr [3];
			
			fBitBuffer = (((((b0 << 8) | b1) << 8) | b2) << 8) | b3;
			
			}

		#endif

		fBitBufferCount = 32;
		
		fByteOffset += 4;

		// Number of additional bits we need
		
		const int32 bitsUsed = codeSize - bitsSoFar;

		// Number of low order bits in the current buffer we don't care about
		
		const int32 bitsNotUsed = 32 - bitsUsed;

		code |= fBitBuffer >> bitsNotUsed;

		fBitBuffer     <<= bitsUsed;
		fBitBufferCount -= bitsUsed;

		}

	return true;

	}

/******************************************************************************/

bool dng_lzw_expander::Expand (const uint8 *sPtr,
						       uint8 *dPtr,
						       int32 sCount,
						       int32 dCount)
	{
	
	void *dStartPtr = dPtr;
	
	fSrcPtr = sPtr;
	
	fSrcCount = sCount;
	
	fByteOffset = 0;
	
	/* the master decode loop */
	
	while (true)
		{
		
		InitTable ();
			
		int32 code;
			
		do
			{
			
			if (!GetCodeWord (code)) 
				return false;
				
			DNG_ASSERT (code <= fNextCode,
						"Unexpected LZW code in dng_lzw_expander::Expand");
			
			}
		while (code == kResetCode);
		
		if (code == kEndCode) 
			return true;
		
		if (code > kEndCode) 
			return false;
		
		int32 oldCode = code;
		int32 inChar  = code;
		
		*(dPtr++) = (uint8) code;
		
		if (--dCount == 0) 
			return true;
		
		while (true)
			{
			
			if (!GetCodeWord (code)) 
				return false;

			if (code == kResetCode) 
				break;
			
			if (code == kEndCode) 
				return true;
			
			const int32 inCode = code;

			bool repeatLastPixel = false;
			
			if (code >= fNextCode)
				{
					
				// This is either a bad file or our code table is not big enough; we
				// are going to repeat the last code seen and attempt to muddle thru.
				
				code = oldCode;	

				repeatLastPixel = true;

				}
			
			// this can only happen if we hit 2 bad codes in a row
			
			if (code > fNextCode)
				return false;

			const int32 depth = fTable [code].depth;

			if (depth < dCount)
				{

				dCount -= depth;
				
				dPtr += depth;

				uint8 *ptr = dPtr;
				
				// give the compiler an extra hint to optimize these as registers
				
				const LZWExpanderNode *localTable = fTable;
				
				int32 localCode = code;
				
				// this is usually the hottest loop in LZW expansion
				
				while (localCode >= kResetCode)
					{
					
					if (ptr <= dStartPtr)
						return false;		// about to trash memory

					const LZWExpanderNode &node = localTable [localCode];
					
					uint8 tempFinal = (uint8) node.final;
					
					localCode = node.prefix;

					// Check for bogus table entry

					if (localCode < 0 || localCode > kTableSize)
						return false;

					*(--ptr) = tempFinal;

					}
				
				code = localCode;

				inChar = localCode;

				if (ptr <= dStartPtr)
					return false;		// about to trash memory

				*(--ptr) = (uint8) inChar;

				}
		
			else
				{
				
				// There might not be enough room for the full code
				// so skip the end of it.
				
				const int32 skip = depth - dCount;

				for (int32 i = 0; i < skip ; i++)
					{
					const LZWExpanderNode &node = fTable [code];
					code = node.prefix;
					}

				int32 depthUsed = depth - skip;

				dCount -= depthUsed;
				
				dPtr += depthUsed;

				uint8 *ptr = dPtr;

				while (code >= 0)
					{
					
					if (ptr <= dStartPtr)
						return false;		// about to trash memory

					const LZWExpanderNode &node = fTable [code];

					*(--ptr) = (uint8) node.final;

					code = node.prefix;
					
					// Check for bogus table entry

					if (code > kTableSize)
						return false;

					}

				return true;

				}

			if (repeatLastPixel)
				{
				
				*(dPtr++) = (uint8) inChar;
				
				if (--dCount == 0)
					return true;
	
				}
				
			if (fNextCode < kTableSize)
				{
				
				AddTable (oldCode, code);
				
				}
			
			oldCode = inCode;
			
			}
			
		}	

	return false;
	
	}

/*****************************************************************************/

dng_row_interleaved_image::dng_row_interleaved_image (dng_image &image,
													  uint32 factor)
													  
	:	dng_image (image.Bounds    (),
				   image.Planes    (),
				   image.PixelType ())
													  
	,	fImage  (image )
	,	fFactor (factor)
	
	{
	
	}
	
/*****************************************************************************/

int32 dng_row_interleaved_image::MapRow (int32 row) const
	{

	uint32 rows = Height ();
	
	int32 top = Bounds ().t;
	
	uint32 fieldRow = row - top;
	
	for (uint32 field = 0; true; field++)
		{
		
		uint32 fieldRows = (rows - field + fFactor - 1) / fFactor;
		
		if (fieldRow < fieldRows)
			{
			
			return fieldRow * fFactor + field + top;
			
			}
			
		fieldRow -= fieldRows;
		
		}
		
	ThrowProgramError ();
		
	return 0;
	
	}
	
/*****************************************************************************/

void dng_row_interleaved_image::DoGet (dng_pixel_buffer &buffer) const
	{
	
	dng_pixel_buffer tempBuffer (buffer);
	
	for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
		{
				
		tempBuffer.fArea.t = MapRow (row);
		
		tempBuffer.fArea.b = tempBuffer.fArea.t + 1;
		
		tempBuffer.fData = (void *) buffer.DirtyPixel (row,
										 			   buffer.fArea.l,
										 			   buffer.fPlane);
										 
		fImage.Get (tempBuffer);
		
		}
		
	}
	
/*****************************************************************************/

void dng_row_interleaved_image::DoPut (const dng_pixel_buffer &buffer)
	{
	
	dng_pixel_buffer tempBuffer (buffer);
	
	for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
		{
				
		tempBuffer.fArea.t = MapRow (row);
		
		tempBuffer.fArea.b = tempBuffer.fArea.t + 1;
		
		tempBuffer.fData = (void *) buffer.ConstPixel (row,
										 			   buffer.fArea.l,
										 			   buffer.fPlane);
										 
		fImage.Put (tempBuffer);
		
		}
		
	}
	
/*****************************************************************************/

static void ReorderSubTileBlocks (dng_host &host,
								  const dng_ifd &ifd,
								  dng_pixel_buffer &buffer,
								  AutoPtr<dng_memory_block> &tempBuffer)
	{
	
	uint32 tempBufferSize = ComputeBufferSize(buffer.fPixelType,
											  buffer.fArea.Size(),
											  buffer.fPlanes, padNone);
							
	if (!tempBuffer.Get () || tempBuffer->LogicalSize () < tempBufferSize)
		{
		
		tempBuffer.Reset (host.Allocate (tempBufferSize));
		
		}
	
	uint32 blockRows = ifd.fSubTileBlockRows;
	uint32 blockCols = ifd.fSubTileBlockCols;
	
	uint32 rowBlocks = buffer.fArea.H () / blockRows;
	uint32 colBlocks = buffer.fArea.W () / blockCols;
	
	int32 rowStep = buffer.fRowStep * buffer.fPixelSize;
	int32 colStep = buffer.fColStep * buffer.fPixelSize;
	
	int32 rowBlockStep = rowStep * blockRows;
	int32 colBlockStep = colStep * blockCols;
	
	uint32 blockColBytes = blockCols * buffer.fPlanes * buffer.fPixelSize;
	
	const uint8 *s0 = (const uint8 *) buffer.fData;
	      uint8 *d0 = tempBuffer->Buffer_uint8 ();
	
	for (uint32 rowBlock = 0; rowBlock < rowBlocks; rowBlock++)
		{
		
		uint8 *d1 = d0;
		
		for (uint32 colBlock = 0; colBlock < colBlocks; colBlock++)
			{
			
			uint8 *d2 = d1;
			
			for (uint32 blockRow = 0; blockRow < blockRows; blockRow++)
				{
				
				for (uint32 j = 0; j < blockColBytes; j++)
					{
					
					d2 [j] = s0 [j];
					
					}
					
				s0 += blockColBytes;
				
				d2 += rowStep;
				
				}
			
			d1 += colBlockStep;
			
			}
			
		d0 += rowBlockStep;
		
		}
		
	// Copy back reordered pixels.
		
	DoCopyBytes (tempBuffer->Buffer (),
				 buffer.fData,
				 tempBufferSize);
	
	}

/*****************************************************************************/

class dng_image_spooler: public dng_spooler
	{
	
	private:
	
		dng_host &fHost;
		
		const dng_ifd &fIFD;
	
		dng_image &fImage;
	
		dng_rect fTileArea;
		
		uint32 fPlane;
		uint32 fPlanes;
		
		dng_memory_block &fBlock;
		
		AutoPtr<dng_memory_block> &fSubTileBuffer;
		
		dng_rect fTileStrip;
		
		uint8 *fBuffer;
		
		uint32 fBufferCount;
		uint32 fBufferSize;
		
	public:
	
		dng_image_spooler (dng_host &host,
						   const dng_ifd &ifd,
						   dng_image &image,
						   const dng_rect &tileArea,
						   uint32 plane,
						   uint32 planes,
						   dng_memory_block &block,
						   AutoPtr<dng_memory_block> &subTileBuffer);
		
		virtual ~dng_image_spooler ();
			
		virtual void Spool (const void *data,
							uint32 count);
							
	private:
	
		// Hidden copy constructor and assignment operator.
	
		dng_image_spooler (const dng_image_spooler &spooler);
		
		dng_image_spooler & operator= (const dng_image_spooler &spooler);
	
	};

/*****************************************************************************/

dng_image_spooler::dng_image_spooler (dng_host &host,
									  const dng_ifd &ifd,
									  dng_image &image,
									  const dng_rect &tileArea,
									  uint32 plane,
									  uint32 planes,
									  dng_memory_block &block,
									  AutoPtr<dng_memory_block> &subTileBuffer)

	:	fHost (host)
	,	fIFD (ifd)
	,	fImage (image)
	,	fTileArea (tileArea)
	,	fPlane (plane)
	,	fPlanes (planes)
	,	fBlock (block)
	,	fSubTileBuffer (subTileBuffer)
	
	,	fTileStrip ()
	,	fBuffer (NULL)
	,	fBufferCount (0)
	,	fBufferSize (0)
	
	{
	
	uint32 bytesPerRow = fTileArea.W () * fPlanes * (uint32) sizeof (uint16);
	
	uint32 stripLength = Pin_uint32 (ifd.fSubTileBlockRows,
									 fBlock.LogicalSize () / bytesPerRow,
									 fTileArea.H ());
									
	stripLength = stripLength / ifd.fSubTileBlockRows
							  * ifd.fSubTileBlockRows;
	
	fTileStrip   = fTileArea;
	fTileStrip.b = fTileArea.t + stripLength;
	
	fBuffer = (uint8 *) fBlock.Buffer ();
	
	fBufferCount = 0;
	fBufferSize  = bytesPerRow * stripLength;
				  
	}

/*****************************************************************************/

dng_image_spooler::~dng_image_spooler ()
	{
	
	}

/*****************************************************************************/

void dng_image_spooler::Spool (const void *data,
							   uint32 count)
	{
	
	while (count)
		{
		
		uint32 block = Min_uint32 (count, fBufferSize - fBufferCount);
		
		if (block == 0)
			{
			return;
			}
		
		DoCopyBytes (data,
					 fBuffer + fBufferCount,
				     block);
				
		data = ((const uint8 *) data) + block;
		
		count -= block;
		
		fBufferCount += block;
		
		if (fBufferCount == fBufferSize)
			{
			
			fHost.SniffForAbort ();
			
			dng_pixel_buffer buffer (fTileStrip, fPlane, fPlanes, ttShort,
				 pcInterleaved, fBuffer);
			
			if (fIFD.fSubTileBlockRows > 1)
				{
			
				ReorderSubTileBlocks (fHost,
									  fIFD,
									  buffer,
									  fSubTileBuffer);
									  
				}

			fImage.Put (buffer);
			
			uint32 stripLength = fTileStrip.H ();
			
			fTileStrip.t = fTileStrip.b;
			
			fTileStrip.b = Min_int32 (fTileStrip.t + stripLength,
									  fTileArea.b);
			
			fBufferCount = 0;
			
			fBufferSize = fTileStrip.W () *
						  fTileStrip.H () *
						  fPlanes * (uint32) sizeof (uint16);
	
			}

		}
	
	}

/*****************************************************************************/

dng_read_image::dng_read_image ()

	:	fJPEGTables ()
	
	{
	
	}

/*****************************************************************************/

dng_read_image::~dng_read_image ()
	{
	
	}

/*****************************************************************************/

bool dng_read_image::ReadUncompressed (dng_host &host,
									   const dng_ifd &ifd,
									   dng_stream &stream,
									   dng_image &image,
									   const dng_rect &tileArea,
									   uint32 plane,
									   uint32 planes,
									   AutoPtr<dng_memory_block> &uncompressedBuffer,
									   AutoPtr<dng_memory_block> &subTileBlockBuffer)
	{
	
	uint32 rows          = tileArea.H ();
	uint32 samplesPerRow = tileArea.W ();
	
	if (ifd.fPlanarConfiguration == pcRowInterleaved)
		{
		rows = SafeUint32Mult(rows, planes);
		}
	else
		{
		samplesPerRow = SafeUint32Mult(samplesPerRow, planes);
		}
	
	uint32 samplesPerTile = SafeUint32Mult(samplesPerRow, rows);
		
	if (uncompressedBuffer.Get () == NULL)
		{

		#if qDNGValidate
		
		ReportError ("Fuzz: Missing uncompressed buffer");
		
		#endif

		ThrowBadFormat ();
		
		}
	
	uint32 bitDepth = ifd.fBitsPerSample [plane];
	
	uint32 pixelType = ttUndefined;
	
	if (bitDepth == 8)
		{
		
		pixelType = ttByte;
				
		stream.Get (uncompressedBuffer->Buffer (), samplesPerTile);
		
		}
		
	else if (bitDepth == 16 && ifd.fSampleFormat [0] == sfFloatingPoint)
		{
		
		pixelType = ttFloat;
		
		uint32 *p_uint32 = (uint32 *) uncompressedBuffer->Buffer ();
			
		for (uint32 j = 0; j < samplesPerTile; j++)
			{
			
			p_uint32 [j] = DNG_HalfToFloat (stream.Get_uint16 ());
											
			}
		
		}
	
	else if (bitDepth == 24 && ifd.fSampleFormat [0] == sfFloatingPoint)
		{
		
		pixelType = ttFloat;
		
		uint32 *p_uint32 = (uint32 *) uncompressedBuffer->Buffer ();
			
		for (uint32 j = 0; j < samplesPerTile; j++)
			{
			
			uint8 input [3];
			
			if (stream.LittleEndian ())
				{
				input [2] = stream.Get_uint8 ();
				input [1] = stream.Get_uint8 ();
				input [0] = stream.Get_uint8 ();
				}
				
			else
				{
				input [0] = stream.Get_uint8 ();
				input [1] = stream.Get_uint8 ();
				input [2] = stream.Get_uint8 ();
				}
			
			p_uint32 [j] = DNG_FP24ToFloat (input);
											
			}
		
		}
	
	else if (bitDepth == 16)
		{
		
		pixelType = ttShort;
		
		stream.Get (uncompressedBuffer->Buffer (), samplesPerTile * 2);
		
		if (stream.SwapBytes ())
			{
			
			DoSwapBytes16 ((uint16 *) uncompressedBuffer->Buffer (),
						   samplesPerTile);
						
			}
				
		}
		
	else if (bitDepth == 32)
		{
		
		pixelType = image.PixelType ();
		
		stream.Get (uncompressedBuffer->Buffer (), samplesPerTile * 4);
		
		if (stream.SwapBytes ())
			{
			
			DoSwapBytes32 ((uint32 *) uncompressedBuffer->Buffer (),
						   samplesPerTile);
						
			}
				
		}
		
	else if (bitDepth == 12)
		{
		
		pixelType = ttShort;
		
		uint16 *p = (uint16 *) uncompressedBuffer->Buffer ();
			
		uint32 evenSamples = samplesPerRow >> 1;
		
		for (uint32 row = 0; row < rows; row++)
			{
			
			for (uint32 j = 0; j < evenSamples; j++)
				{
				
				uint32 b0 = stream.Get_uint8 ();
				uint32 b1 = stream.Get_uint8 ();
				uint32 b2 = stream.Get_uint8 ();
								
				p [0] = (uint16) ((b0 << 4) | (b1 >> 4));
				p [1] = (uint16) (((b1 << 8) | b2) & 0x0FFF);
				
				p += 2;
				
				}
				
			if (samplesPerRow & 1)
				{
				
				uint32 b0 = stream.Get_uint8 ();
				uint32 b1 = stream.Get_uint8 ();
				
				p [0] = (uint16) ((b0 << 4) | (b1 >> 4));
				
				p += 1;
				
				}
			
			}
		
		}
		
	else if (bitDepth > 8 && bitDepth < 16)
		{
		
		pixelType = ttShort;
		
		uint16 *p = (uint16 *) uncompressedBuffer->Buffer ();
			
		uint32 bitMask = (1 << bitDepth) - 1;
							
		for (uint32 row = 0; row < rows; row++)
			{
			
			uint32 bitBuffer  = 0;
			uint32 bufferBits = 0;
			
			for (uint32 j = 0; j < samplesPerRow; j++)
				{
				
				while (bufferBits < bitDepth)
					{
					
					bitBuffer = (bitBuffer << 8) | stream.Get_uint8 ();
					
					bufferBits += 8;
					
					}
									
				p [j] = (uint16) ((bitBuffer >> (bufferBits - bitDepth)) & bitMask);
				
				bufferBits -= bitDepth;
				
				}
				
			p += samplesPerRow;
			
			}
			
		}
		
	else if (bitDepth > 16 && bitDepth < 32)
		{
		
		pixelType = ttLong;
		
		uint32 *p = (uint32 *) uncompressedBuffer->Buffer ();
			
		uint32 bitMask = ((uint32) 1 << bitDepth) - 1;
							
		for (uint32 row = 0; row < rows; row++)
			{
			
			uint64 bitBuffer  = 0;
			uint32 bufferBits = 0;
			
			for (uint32 j = 0; j < samplesPerRow; j++)
				{
				
				while (bufferBits < bitDepth)
					{
					
					bitBuffer = (bitBuffer << 8) | stream.Get_uint8 ();
					
					bufferBits += 8;
					
					}
									
				p [j] = ((uint32) (bitBuffer >> (bufferBits - bitDepth))) & bitMask;
				
				bufferBits -= bitDepth;
				
				}
				
			p += samplesPerRow;
			
			}
			
		}
		
	else
		{
		
		return false;
		
		}
		
	dng_pixel_buffer buffer (tileArea, plane, planes, pixelType,
		 ifd.fPlanarConfiguration, uncompressedBuffer->Buffer ());
	
	if (ifd.fSampleBitShift)
		{
		
		buffer.ShiftRight (ifd.fSampleBitShift);
		
		}
		
	if (ifd.fSubTileBlockRows > 1)
		{
		
		ReorderSubTileBlocks (host,
							  ifd,
							  buffer,
							  subTileBlockBuffer);
		
		}
		
	image.Put (buffer);
	
	return true;
		
	}

/*****************************************************************************/

#if qDNGUseLibJPEG

/*****************************************************************************/

static void dng_error_exit (j_common_ptr cinfo)
	{
	
	// Output message.
	
	(*cinfo->err->output_message) (cinfo);
	
	// Convert to a dng_exception.

	switch (cinfo->err->msg_code)
		{
		
		case JERR_OUT_OF_MEMORY:
			{
			ThrowMemoryFull ();
			break;
			}
			
		default:
			{
			ThrowBadFormat ();
			}
			
		}
			
	}

/*****************************************************************************/

static void dng_output_message (j_common_ptr cinfo)
	{
	
	// Format message to string.
	
	char buffer [JMSG_LENGTH_MAX];

	(*cinfo->err->format_message) (cinfo, buffer);
	
	// Report the libjpeg message as a warning.
	
	ReportWarning ("libjpeg", buffer);

	}

/*****************************************************************************/

#endif

/*****************************************************************************/

void dng_read_image::DecodeLossyJPEG (dng_host &host,
									  dng_image &image,
									  const dng_rect &tileArea,
									  uint32 plane,
									  uint32 planes,
									  uint32 /* photometricInterpretation */,
									  uint32 jpegDataSize,
									  uint8 *jpegDataInMemory)
	{
	
	#if qDNGUseLibJPEG
	
	struct jpeg_decompress_struct cinfo;
	
	// Setup the error manager.
	
	struct jpeg_error_mgr jerr;

	cinfo.err = jpeg_std_error (&jerr);
	
	jerr.error_exit     = dng_error_exit;
	jerr.output_message = dng_output_message;
	
	try
		{
		
		// Create the decompression context.

		jpeg_create_decompress (&cinfo);
		
		// Set up the memory data source manager.
		
		size_t jpegDataSizeAsSizet = 0;
		ConvertUnsigned(jpegDataSize, &jpegDataSizeAsSizet);
		jpeg_source_mgr memorySource =
			CreateJpegMemorySource(jpegDataInMemory,
								   jpegDataSizeAsSizet);
		cinfo.src = &memorySource;
			
		// Read the JPEG header.
			
		jpeg_read_header (&cinfo, TRUE);
		
		// Check header.
		
		{
			// Number of components may not be negative.
			if (cinfo.num_components < 0)
				{
				ThrowBadFormat();
				}
			
			// Convert relevant values from header to uint32.
			uint32 imageWidthAsUint32 = 0;
			uint32 imageHeightAsUint32 = 0;
			uint32 numComponentsAsUint32 = 0;
			ConvertUnsigned(cinfo.image_width, &imageWidthAsUint32);
			ConvertUnsigned(cinfo.image_height, &imageHeightAsUint32);
			// num_components is an int. Casting to unsigned is safe because the
			// test above guarantees num_components is not negative.
			ConvertUnsigned(static_cast<unsigned>(cinfo.num_components),
							&numComponentsAsUint32);
			
			// Check that dimensions of JPEG correspond to dimensions of tile.
			if (imageWidthAsUint32    != tileArea.W () ||
			    imageHeightAsUint32   != tileArea.H () ||
			    numComponentsAsUint32 != planes        )
				{
				ThrowBadFormat ();
				}
		}
			
		// Start the compression.
		
		jpeg_start_decompress (&cinfo);
		
		// Setup a one-scanline size buffer.
		
		dng_pixel_buffer buffer(tileArea, plane, planes, ttByte, pcInterleaved,
								NULL);
		buffer.fArea.b = tileArea.t + 1;
		
		buffer.fDirty = true;
		
		AutoPtr<dng_memory_block> bufferData (host.Allocate (buffer.fRowStep));
		
		buffer.fData = bufferData->Buffer ();
		
		uint8 *sampArray [1];
		
		sampArray [0] = bufferData->Buffer_uint8 ();
		
		// Read each scanline and save to image.
			
		while (buffer.fArea.t < tileArea.b)
			{
			
			jpeg_read_scanlines (&cinfo, sampArray, 1);
			
			image.Put (buffer);
			
			buffer.fArea.t = buffer.fArea.b;
			buffer.fArea.b = buffer.fArea.t + 1;
			
			}
			
		// Cleanup.
			
		jpeg_finish_decompress (&cinfo);

		jpeg_destroy_decompress (&cinfo);
			
		}
		
	catch (...)
		{
		
		jpeg_destroy_decompress (&cinfo);
		
		throw;
		
		}
	
	#else
				
	// The dng_sdk does not include a lossy JPEG decoder.  Override this
	// this method to add lossy JPEG support.
	
	(void) host;
	(void) image;
	(void) tileArea;
	(void) plane;
	(void) planes;
	(void) jpegDataSize;
	(void) jpegDataInMemory;
	
	ThrowProgramError ("Missing lossy JPEG decoder");
	
	#endif
	
	}
	
/*****************************************************************************/

static dng_memory_block * ReadJPEGDataToBlock (dng_host &host,
											   dng_stream &stream,
											   dng_memory_block *tablesBlock,
											   uint64 tileOffset,
											   uint32 tileByteCount,
											   bool patchFirstByte)
	{
	
	// This ensures that the "tileByteCount -= 2" operation below will not wrap
	// around.
	if (tileByteCount <= 2)
		{
		ThrowEndOfFile ();
		}
		
	uint32 tablesByteCount = tablesBlock ? tablesBlock->LogicalSize () : 0;
	
	// This ensures that the "tablesByteCount -= 2" operation below will not
	// wrap around.
	if (tablesByteCount && tablesByteCount < 4)
		{
		ThrowEndOfFile ();
		}
		
	// The JPEG tables start with a two byte SOI marker, and
	// and end with a two byte EOI marker.  The JPEG tile
	// data also starts with a two byte SOI marker.  We can
	// convert this combination a normal JPEG stream removing
	// the last two bytes of the JPEG tables and the first two
	// bytes of the tile data, and then concatenating them.

	if (tablesByteCount)
		{
		
		// Ensure the "tileOffset += 2" operation below will not wrap around.
		if (tileOffset > std::numeric_limits<uint64>::max () - 2)
			{
			ThrowEndOfFile();
			}
		
		tablesByteCount -= 2;
		
		tileOffset    += 2;
		tileByteCount -= 2;
		
		}
		
	// Allocate buffer.
	
	AutoPtr<dng_memory_block> buffer (host.Allocate (
		SafeUint32Add(tablesByteCount, tileByteCount)));
													  
	// Read in table.
	
	if (tablesByteCount)
		{
		
		DoCopyBytes (tablesBlock->Buffer (),
					 buffer->Buffer (),
					 tablesByteCount);
		
		}
		
	// Read in tile data.
	
	stream.SetReadPosition (tileOffset);
	
	stream.Get (buffer->Buffer_uint8 () + tablesByteCount, tileByteCount);
		
	// Patch first byte, if required.
		
	if (patchFirstByte)
		{
		
		buffer->Buffer_uint8 () [0] = 0xFF;
		
		}
		
	// Return buffer.
	
	return buffer.Release ();
	
	}

/*****************************************************************************/

bool dng_read_image::ReadBaselineJPEG (dng_host &host,
									   const dng_ifd &ifd,
									   dng_stream &stream,
									   dng_image &image,
									   const dng_rect &tileArea,
									   uint32 plane,
									   uint32 planes,
									   uint32 tileByteCount,
									   uint8 *jpegDataInMemory)
	{
	
	// Setup the data source.
	
	if (fJPEGTables.Get () || !jpegDataInMemory)
		{
		
		AutoPtr<dng_memory_block> jpegDataBlock;
	
		jpegDataBlock.Reset (ReadJPEGDataToBlock (host,
												  stream,
												  fJPEGTables.Get (),
												  stream.Position (),
												  tileByteCount,
												  ifd.fPatchFirstJPEGByte));
												  
		DecodeLossyJPEG (host,
						 image,
						 tileArea,
						 plane,
						 planes,
						 ifd.fPhotometricInterpretation,
						 jpegDataBlock->LogicalSize (),
						 jpegDataBlock->Buffer_uint8 ());
		
		}
		
	else
		{
	
		if (ifd.fPatchFirstJPEGByte && tileByteCount)
			{
			jpegDataInMemory [0] = 0xFF;
			}

		DecodeLossyJPEG (host,
						 image,
						 tileArea,
						 plane,
						 planes,
						 ifd.fPhotometricInterpretation,
						 tileByteCount,
						 jpegDataInMemory);
		
		}
				
	return true;
	
	}
	
/*****************************************************************************/

bool dng_read_image::ReadLosslessJPEG (dng_host &host,
									   const dng_ifd &ifd,
									   dng_stream &stream,
									   dng_image &image,
									   const dng_rect &tileArea,
									   uint32 plane,
									   uint32 planes,
									   uint32 tileByteCount,
									   AutoPtr<dng_memory_block> &uncompressedBuffer,
									   AutoPtr<dng_memory_block> &subTileBlockBuffer)
	{
	
	// If the tile area is empty, there's nothing to read.
	if (tileArea.IsEmpty ())
		{
		return true;
		}
	
	uint32 bytesPerRow = SafeUint32Mult (tileArea.W(), planes, 
		static_cast<uint32> (sizeof (uint16)));
	
	uint32 rowsPerStrip = Pin_uint32 (ifd.fSubTileBlockRows,
									  kImageBufferSize / bytesPerRow,
									  tileArea.H ());
									  
	rowsPerStrip = rowsPerStrip / ifd.fSubTileBlockRows
								* ifd.fSubTileBlockRows;
									  
	uint32 bufferSize = SafeUint32Mult (bytesPerRow, rowsPerStrip);
	
	if (uncompressedBuffer.Get () &&
		uncompressedBuffer->LogicalSize () < bufferSize)
		{
		
		uncompressedBuffer.Reset ();
		
		}
		
	if (uncompressedBuffer.Get () == NULL)
		{
		
		uncompressedBuffer.Reset (host.Allocate (bufferSize));
									
		}
	
	dng_image_spooler spooler (host,
							   ifd,
							   image,
							   tileArea,
							   plane,
							   planes,
							   *uncompressedBuffer.Get (),
							   subTileBlockBuffer);
							   
	uint32 decodedSize = SafeUint32Mult(tileArea.W (),
						 tileArea.H (),
						 planes, (uint32) sizeof (uint16));
							
	bool bug16 = ifd.fLosslessJPEGBug16;
	
	uint64 tileOffset = stream.Position ();
	
	DecodeLosslessJPEG (stream,
					    spooler,
					    decodedSize,
					    decodedSize,
						bug16);
						
	if (stream.Position () > tileOffset + tileByteCount)
		{
		ThrowBadFormat ();
		}

	return true;
	
	}
	
/*****************************************************************************/

bool dng_read_image::CanReadTile (const dng_ifd &ifd)
	{
	
	if (ifd.fSampleFormat [0] != sfUnsignedInteger &&
		ifd.fSampleFormat [0] != sfFloatingPoint)
		{
		return false;
		}

	switch (ifd.fCompression)
		{
		
		case ccUncompressed:
			{
			
			if (ifd.fSampleFormat [0] == sfFloatingPoint)
				{
				
				return (ifd.fBitsPerSample [0] == 16 ||
						ifd.fBitsPerSample [0] == 24 ||
						ifd.fBitsPerSample [0] == 32);
						
				}
				
			return ifd.fBitsPerSample [0] >= 8 &&
				   ifd.fBitsPerSample [0] <= 32;
			
			}
			
		case ccJPEG:
			{
			
			if (ifd.fSampleFormat [0] != sfUnsignedInteger)
				{
				return false;
				}
				
			if (ifd.IsBaselineJPEG ())
				{
				
				// Baseline JPEG.
				
				return true;
				
				}
				
			else
				{
				
				// Lossless JPEG.
				
				return ifd.fBitsPerSample [0] >= 8 &&
					   ifd.fBitsPerSample [0] <= 16;
				
				}
				
			break;
			
			}

		case ccLZW:
		case ccDeflate:
		case ccOldDeflate:
		case ccPackBits:
			{
			
			if (ifd.fSampleFormat [0] == sfFloatingPoint)
				{
				
				if (ifd.fCompression == ccPackBits)
					{
					return false;
					}
				
				if (ifd.fPredictor != cpNullPredictor   &&
					ifd.fPredictor != cpFloatingPoint   &&
					ifd.fPredictor != cpFloatingPointX2 &&
					ifd.fPredictor != cpFloatingPointX4)
					{
					return false;
					}
					
				if (ifd.fBitsPerSample [0] != 16 &&
					ifd.fBitsPerSample [0] != 24 &&
					ifd.fBitsPerSample [0] != 32)
					{
					return false;
					}
				
				}
				
			else
				{

				if (ifd.fPredictor != cpNullPredictor		   &&
					ifd.fPredictor != cpHorizontalDifference   &&
					ifd.fPredictor != cpHorizontalDifferenceX2 &&
					ifd.fPredictor != cpHorizontalDifferenceX4)
					{
					return false;
					}
					
				if (ifd.fBitsPerSample [0] != 8  &&
					ifd.fBitsPerSample [0] != 16 &&
					ifd.fBitsPerSample [0] != 32)
					{
					return false;
					}
					
				}
					
			return true;
			
			}
			
		default:
			{
			break;
			}
			
		}
		
	return false;

	}
	
/*****************************************************************************/

bool dng_read_image::NeedsCompressedBuffer (const dng_ifd &ifd)
	{
	
	if (ifd.fCompression == ccLZW        ||
		ifd.fCompression == ccDeflate    ||
		ifd.fCompression == ccOldDeflate ||
		ifd.fCompression == ccPackBits)
		{
		return true;
		}
		
	return false;
	
	}
	
/*****************************************************************************/

void dng_read_image::ByteSwapBuffer (dng_host & /* host */,
									 dng_pixel_buffer &buffer)
	{
	
	uint32 pixels = buffer.fRowStep * buffer.fArea.H ();
	
	switch (buffer.fPixelSize)
		{
		
		case 2:
			{
			
			DoSwapBytes16 ((uint16 *) buffer.fData,
						   pixels);
						   
			break;
			
			}
			
		case 4:
			{
			
			DoSwapBytes32 ((uint32 *) buffer.fData,
						   pixels);
						   
			break;
			
			}
			
		default:
			break;
			
		}

	}
						    
/*****************************************************************************/

void dng_read_image::DecodePredictor (dng_host & /* host */,
									  const dng_ifd &ifd,
						        	  dng_pixel_buffer &buffer)
	{
	
	switch (ifd.fPredictor)
		{
		
		case cpNullPredictor:
			{
			
			return;
			
			}
		
		case cpHorizontalDifference:
		case cpHorizontalDifferenceX2:
		case cpHorizontalDifferenceX4:
			{
			
			int32 xFactor = 1;
			
			if (ifd.fPredictor == cpHorizontalDifferenceX2)
				{
				xFactor = 2;
				}
				
			else if (ifd.fPredictor == cpHorizontalDifferenceX4)
				{
				xFactor = 4;
				}
			
			switch (buffer.fPixelType)
				{
				
				case ttByte:
					{
					
					DecodeDelta8 ((uint8 *) buffer.fData,
								  buffer.fArea.H (),
								  buffer.fArea.W () / xFactor,
								  buffer.fPlanes    * xFactor);
					
					return;
					
					}
					
				case ttShort:
					{
					
					DecodeDelta16 ((uint16 *) buffer.fData,
								   buffer.fArea.H (),
								   buffer.fArea.W () / xFactor,
								   buffer.fPlanes    * xFactor);
					
					return;
					
					}
					
				case ttLong:
					{
					
					DecodeDelta32 ((uint32 *) buffer.fData,
								   buffer.fArea.H (),
								   buffer.fArea.W () / xFactor,
								   buffer.fPlanes    * xFactor);
					
					return;
					
					}
					
				default:
					break;
					
				}
			
			break;
			
			}
			
		default:
			break;
		
		}
		
	ThrowBadFormat ();
	
	}
						    
/*****************************************************************************/

void dng_read_image::ReadTile (dng_host &host,
						       const dng_ifd &ifd,
						       dng_stream &stream,
						       dng_image &image,
						       const dng_rect &tileArea,
						       uint32 plane,
						       uint32 planes,
						       uint32 tileByteCount,
							   AutoPtr<dng_memory_block> &compressedBuffer,
							   AutoPtr<dng_memory_block> &uncompressedBuffer,
							   AutoPtr<dng_memory_block> &subTileBlockBuffer)
	{
	
	switch (ifd.fCompression)
		{
		
		case ccLZW:
		case ccDeflate:
		case ccOldDeflate:
		case ccPackBits:
			{
			
			// Figure out uncompressed size.
			
			uint32 bytesPerSample = (ifd.fBitsPerSample [0] >> 3);
			
			uint32 rowStep = 0;
			
			uint32 sampleCount = 0;
				
			if (!SafeUint32Mult (planes, tileArea.W (), &rowStep) ||
				 !SafeUint32Mult (rowStep, tileArea.H (), &sampleCount))
				{
				
				ThrowMemoryFull ("Arithmetic overflow computing sample count.");
				
				}
				
			// Setup pixel buffer to hold uncompressed data.
			
			uint32 pixelType = ttUndefined;
			
			if (ifd.fSampleFormat [0] == sfFloatingPoint)
				{
				pixelType = ttFloat;
				}
			
			else if (ifd.fBitsPerSample [0] == 8)
				{
				pixelType = ttByte;
				}
				
			else if (ifd.fBitsPerSample [0] == 16)
				{
				pixelType = ttShort;
				}
				
			else if (ifd.fBitsPerSample [0] == 32)
				{
				pixelType = ttLong;
				}
				
			else
				{
				ThrowBadFormat ();
				}
				
			uint32 uncompressedSize = ComputeBufferSize (pixelType, tileArea.Size(),
				 planes, padNone);
			
			dng_pixel_buffer buffer (tileArea, plane, planes, pixelType, pcInterleaved,
				 NULL);
			
			uint32 bufferSize = uncompressedSize;
			
			// If we are using the floating point predictor, we need an extra
			// buffer row.
			
			if (ifd.fPredictor == cpFloatingPoint   ||
				ifd.fPredictor == cpFloatingPointX2 ||
				ifd.fPredictor == cpFloatingPointX4)
				{
				uint32 rowSize = 0;
				if (!SafeUint32Mult (rowStep, buffer.fPixelSize, &rowSize) ||
					 !SafeUint32Add (bufferSize, rowSize, &bufferSize))
					{
					
					ThrowMemoryFull ("Arithmetic overflow computing buffer size.");
					
					}
				}
				
			// If are processing less than full size floating point data,
			// we need space to expand the data to full floating point size.
			
			if (buffer.fPixelType == ttFloat)
				{
				bufferSize = Max_uint32 (bufferSize,
										 SafeUint32Mult(sampleCount, 4));
				}
			
			// Sometimes with multi-threading and planar image using strips, 
			// we can process a small tile before a large tile on a thread. 
			// Simple fix is to just reallocate the buffer if it is too small.
			
			if (uncompressedBuffer.Get () &&
				uncompressedBuffer->LogicalSize () < bufferSize)
				{
				
				uncompressedBuffer.Reset ();
				
				}
				
			if (uncompressedBuffer.Get () == NULL)
				{

				uncompressedBuffer.Reset (host.Allocate (bufferSize));
											
				}
				
			buffer.fData = uncompressedBuffer->Buffer ();
			
			// If using floating point predictor, move buffer pointer to second row.
			
			if (ifd.fPredictor == cpFloatingPoint   ||
				ifd.fPredictor == cpFloatingPointX2 ||
				ifd.fPredictor == cpFloatingPointX4)
				{
				
				buffer.fData = (uint8 *) buffer.fData +
							   buffer.fRowStep * buffer.fPixelSize;
							   
				}
			
			// Decompress the data.
			
			if (ifd.fCompression == ccLZW)
				{
				
				dng_lzw_expander expander;
				
				if (!expander.Expand (compressedBuffer->Buffer_uint8 (),
									  (uint8 *) buffer.fData,
									  tileByteCount,
									  uncompressedSize))
					{
					ThrowBadFormat ();
					}
				
				}
				
			else if (ifd.fCompression == ccPackBits)
				{
				
				dng_stream subStream (compressedBuffer->Buffer_uint8 (),
									  tileByteCount);
											 
				if (!DecodePackBits (subStream,
									 (uint8 *) buffer.fData,
									 uncompressedSize))
					{
					ThrowBadFormat ();
					}
				
				}
			
			else
				{
				
				uLongf dstLen = uncompressedSize;
							
				int err = uncompress ((Bytef *) buffer.fData,
									  &dstLen,
									  (const Bytef *) compressedBuffer->Buffer (),
									  tileByteCount);
									  
				if (err != Z_OK)
					{
					
					if (err == Z_MEM_ERROR)
						{
						ThrowMemoryFull ();
						}
						
					else if (err == Z_DATA_ERROR)
						{
						// Most other TIFF readers do not fail for this error
						// so we should not either, even if it means showing
						// a corrupted image to the user.  Watson #2530216
						// - tknoll 12/20/11
						}
						
					else
						{
						ThrowBadFormat ();
						}
						
					}
					
				if (dstLen != uncompressedSize)
					{
					ThrowBadFormat ();
					}
				
				}
				
			// The floating point predictor is byte order independent.
			
			if (ifd.fPredictor == cpFloatingPoint   ||
				ifd.fPredictor == cpFloatingPointX2 ||
				ifd.fPredictor == cpFloatingPointX4)
				{
				
				int32 xFactor = 1;
				
				if (ifd.fPredictor == cpFloatingPointX2)
					{
					xFactor = 2;
					}
					
				else if (ifd.fPredictor == cpFloatingPointX4)
					{
					xFactor = 4;
					}
				
				for (int32 row = tileArea.t; row < tileArea.b; row++)
					{
					
					uint8 *srcPtr = (uint8 *) buffer.DirtyPixel (row    , tileArea.l, plane);
					// Destination is previous row.
					// Subtracting buffer.fRowStep * buffer.fPixelSize will
					// always result in a pointer that lies inside the buffer
					// because above, we added exactly the same offset to
					// buffer.fData (see the piece of code commented "move
					// buffer pointer to second row").
					uint8 *dstPtr = srcPtr -
						buffer.fRowStep * buffer.fPixelSize;
					
					DecodeFPDelta (srcPtr,
								   dstPtr,
								   tileArea.W () / xFactor,
								   planes        * xFactor,
								   bytesPerSample);
					
					}
				
				buffer.fData = (uint8 *) buffer.fData -
							   buffer.fRowStep * buffer.fPixelSize;
							   
				}
				
			else
				{
				
				// Both these compression algorithms are byte based.
				
				if (stream.SwapBytes ())
					{
					
					ByteSwapBuffer (host,
									buffer);
									
					}
					
				// Undo the predictor.
				
				DecodePredictor (host,
								 ifd,
								 buffer);
								 
				}
				
			// Expand floating point data, if needed.
			
			if (buffer.fPixelType == ttFloat && buffer.fPixelSize == 2)
				{
				
				uint16 *srcPtr = (uint16 *) buffer.fData;
				uint32 *dstPtr = (uint32 *) buffer.fData;
				
				for (int32 index = sampleCount - 1; index >= 0; index--)
					{
					
					dstPtr [index] = DNG_HalfToFloat (srcPtr [index]);
					
					}
					
				buffer.fPixelSize = 4;
				
				}
				
			else if (buffer.fPixelType == ttFloat && buffer.fPixelSize == 3)
				{
				
				uint8  *srcPtr = ((uint8  *) buffer.fData) + (sampleCount - 1) * 3;
				uint32 *dstPtr = ((uint32 *) buffer.fData) + (sampleCount - 1);
				
				if (stream.BigEndian () || ifd.fPredictor == cpFloatingPoint   ||
										   ifd.fPredictor == cpFloatingPointX2 ||
										   ifd.fPredictor == cpFloatingPointX4)
					{
				
					for (uint32 index = 0; index < sampleCount; index++)
						{
						
						*(dstPtr--) = DNG_FP24ToFloat (srcPtr);
						
						srcPtr -= 3;
						
						}
						
					}
					
				else
					{
					
					for (uint32 index = 0; index < sampleCount; index++)
						{
						
						uint8 input [3];
						
						input [2] = srcPtr [0];
						input [1] = srcPtr [1];
						input [0] = srcPtr [2];
						
						*(dstPtr--) = DNG_FP24ToFloat (input);
						
						srcPtr -= 3;
						
						}
						
					}
					
				buffer.fPixelSize = 4;
				
				}
			
			// Save the data.
			
			image.Put (buffer);
			
			return;
			
			}
	
		case ccUncompressed:
			{
			
			if (ReadUncompressed (host,
								  ifd,
								  stream,
								  image,
								  tileArea,
								  plane,
								  planes,
								  uncompressedBuffer,
								  subTileBlockBuffer))
				{
				
				return;
				
				}
				
			break;
			
			}
			
		case ccJPEG:
			{
			
			if (ifd.IsBaselineJPEG ())
				{
				
				// Baseline JPEG.
				
				if (ReadBaselineJPEG (host,
									  ifd,
									  stream,
									  image,
									  tileArea,
									  plane,
									  planes,
									  tileByteCount,
									  compressedBuffer.Get () ? compressedBuffer->Buffer_uint8 () : NULL))
					{
					
					return;
					
					}
				
				}
				
			else
				{
				
				// Otherwise is should be lossless JPEG.
				
				if (ReadLosslessJPEG (host,
									  ifd,
									  stream,
									  image,
									  tileArea,
									  plane,
									  planes,
									  tileByteCount,
									  uncompressedBuffer,
									  subTileBlockBuffer))
					{
					
					return;
					
					}
				
				}
			
			break;
			
			}
			
		case ccLossyJPEG:
			{
			
			if (ReadBaselineJPEG (host,
								  ifd,
								  stream,
								  image,
								  tileArea,
								  plane,
								  planes,
								  tileByteCount,
								  compressedBuffer.Get () ? compressedBuffer->Buffer_uint8 () : NULL))
				{
				
				return;
				
				}
							
			break;
			
			}
			
		default:
			break;
			
		}
		
	ThrowBadFormat ();
		
	}

/*****************************************************************************/

bool dng_read_image::CanRead (const dng_ifd &ifd)
	{
	
	if (ifd.fImageWidth  < 1 ||
		ifd.fImageLength < 1)
		{
		return false;
		}
		
	if (ifd.fSamplesPerPixel < 1)
		{
		return false;
		}
		
	if (ifd.fBitsPerSample [0] < 1)
		{
		return false;
		}
	
	for (uint32 j = 1; j < Min_uint32 (ifd.fSamplesPerPixel,
									   kMaxSamplesPerPixel); j++)
		{
		
		if (ifd.fBitsPerSample [j] !=
			ifd.fBitsPerSample [0])
			{
			return false;
			}
			
		if (ifd.fSampleFormat [j] !=
			ifd.fSampleFormat [0])
			{
			return false;
			}

		}
		
	if ((ifd.fPlanarConfiguration != pcInterleaved   ) &&
		(ifd.fPlanarConfiguration != pcPlanar        ) &&
		(ifd.fPlanarConfiguration != pcRowInterleaved))
		{
		return false;
		}
		
	if (ifd.fUsesStrips == ifd.fUsesTiles)
		{
		return false;
		}
		
	uint32 tileCount = ifd.TilesPerImage ();
	
	if (tileCount < 1)
		{
		return false;
		}
		
	bool needTileByteCounts = (ifd.TileByteCount (ifd.TileArea (0, 0)) == 0);
		
	if (tileCount == 1)
		{
		
		if (needTileByteCounts)
			{
			
			if (ifd.fTileByteCount [0] < 1)
				{
				return false;
				}
			
			}
		
		}
		
	else
		{
		
		if (ifd.fTileOffsetsCount != tileCount)
			{
			return false;
			}
			
		if (needTileByteCounts)
			{
			
			if (ifd.fTileByteCountsCount != tileCount)
				{
				return false;
				}
			
			}
		
		}
		
	if (!CanReadTile (ifd))
		{
		return false;
		}
		
	return true;
	
	}
	
/*****************************************************************************/

class dng_read_tiles_task : public dng_area_task
	{
	
	private:
	
		dng_read_image &fReadImage;
		
		dng_host &fHost;
		
		const dng_ifd &fIFD;
		
		dng_stream &fStream;
		
		dng_image &fImage;
		
		dng_jpeg_image *fJPEGImage;
		
		dng_fingerprint *fJPEGTileDigest;
		
		uint32 fOuterSamples;
		
		uint32 fInnerSamples;
		
		uint32 fTilesDown;
		
		uint32 fTilesAcross;
		
		uint64 *fTileOffset;
		
		uint32 *fTileByteCount;
		
		uint32 fCompressedSize;
		
		uint32 fUncompressedSize;
		
		dng_mutex fMutex;
		
		uint32 fNextTileIndex;
		
	public:
	
		dng_read_tiles_task (dng_read_image &readImage,
							 dng_host &host,
							 const dng_ifd &ifd,
							 dng_stream &stream,
							 dng_image &image,
							 dng_jpeg_image *jpegImage,
							 dng_fingerprint *jpegTileDigest,
							 uint32 outerSamples,
							 uint32 innerSamples,
							 uint32 tilesDown,
							 uint32 tilesAcross,
							 uint64 *tileOffset,
							 uint32 *tileByteCount,
							 uint32 compressedSize,
							 uint32 uncompressedSize)
		
			:	fReadImage        (readImage)
			,	fHost		      (host)
			,	fIFD		      (ifd)
			,	fStream		      (stream)
			,	fImage		      (image)
			,	fJPEGImage		  (jpegImage)
			,	fJPEGTileDigest   (jpegTileDigest)
			,	fOuterSamples     (outerSamples)
			,	fInnerSamples     (innerSamples)
			,	fTilesDown        (tilesDown)
			,	fTilesAcross	  (tilesAcross)
			,	fTileOffset		  (tileOffset)
			,	fTileByteCount	  (tileByteCount)
			,	fCompressedSize   (compressedSize)
			,	fUncompressedSize (uncompressedSize)
			,	fMutex			  ("dng_read_tiles_task")
			,	fNextTileIndex	  (0)
			
			{
			
			fMinTaskArea = 16 * 16;
			fUnitCell    = dng_point (16, 16);
			fMaxTileSize = dng_point (16, 16);
			
			}
	
		void Process (uint32 /* threadIndex */,
					  const dng_rect & /* tile */,
					  dng_abort_sniffer *sniffer)
			{
			
			AutoPtr<dng_memory_block> compressedBuffer;
			AutoPtr<dng_memory_block> uncompressedBuffer;
			AutoPtr<dng_memory_block> subTileBlockBuffer;
			
			if (!fJPEGImage)
				{
				compressedBuffer.Reset (fHost.Allocate (fCompressedSize));
				}
			
			if (fUncompressedSize)
				{
				uncompressedBuffer.Reset (fHost.Allocate (fUncompressedSize));
				}
			
			while (true)
				{
				
				uint32 tileIndex;
				uint32 byteCount;
				
					{
					
					dng_lock_mutex lock (&fMutex);
					
					if (fNextTileIndex == fOuterSamples * fTilesDown * fTilesAcross)
						{
						return;
						}
						
					tileIndex = fNextTileIndex++;
					
					TempStreamSniffer noSniffer (fStream, NULL);

					fStream.SetReadPosition (fTileOffset [tileIndex]);
					
					byteCount = fTileByteCount [tileIndex];
					
					if (fJPEGImage)
						{
						
						fJPEGImage->fJPEGData [tileIndex] . Reset (fHost.Allocate (byteCount));
						
						}
					
					fStream.Get (fJPEGImage ? fJPEGImage->fJPEGData [tileIndex]->Buffer ()
											: compressedBuffer->Buffer (),
								 byteCount);
					
					}
					
				dng_abort_sniffer::SniffForAbort (sniffer);
				
				if (fJPEGTileDigest)
					{
					
					dng_md5_printer printer;
					
					printer.Process (compressedBuffer->Buffer (),
									 byteCount);
									 
					fJPEGTileDigest [tileIndex] = printer.Result ();
					
					}
					
				dng_stream tileStream (fJPEGImage ? fJPEGImage->fJPEGData [tileIndex]->Buffer ()
												  : compressedBuffer->Buffer (),
									   byteCount);
									   
				tileStream.SetLittleEndian (fStream.LittleEndian ());
							
				uint32 plane = tileIndex / (fTilesDown * fTilesAcross);
				
				uint32 rowIndex = (tileIndex - plane * fTilesDown * fTilesAcross) / fTilesAcross;
				
				uint32 colIndex = tileIndex - (plane * fTilesDown + rowIndex) * fTilesAcross;
				
				dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex);
				
				dng_host host (&fHost.Allocator (),
							   sniffer);				// Cannot use sniffer attached to main host
				
				fReadImage.ReadTile (host,
									 fIFD,
									 tileStream,
									 fImage,
									 tileArea,
									 plane,
									 fInnerSamples,
									 byteCount,
									 fJPEGImage ? fJPEGImage->fJPEGData [tileIndex]
												: compressedBuffer,
									 uncompressedBuffer,
									 subTileBlockBuffer);
					
				}
			
			}
		
	private:

		// Hidden copy constructor and assignment operator.

		dng_read_tiles_task (const dng_read_tiles_task &);

		dng_read_tiles_task & operator= (const dng_read_tiles_task &);
		
	};

/*****************************************************************************/

void dng_read_image::Read (dng_host &host,
						   const dng_ifd &ifd,
						   dng_stream &stream,
						   dng_image &image,
						   dng_jpeg_image *jpegImage,
						   dng_fingerprint *jpegDigest)
	{
	
	uint32 tileIndex;
	
	// Deal with row interleaved images.
	
	if (ifd.fRowInterleaveFactor > 1 &&
		ifd.fRowInterleaveFactor < ifd.fImageLength)
		{
		
		dng_ifd tempIFD (ifd);
		
		tempIFD.fRowInterleaveFactor = 1;
		
		dng_row_interleaved_image tempImage (image,
											 ifd.fRowInterleaveFactor);
		
		Read (host,
			  tempIFD,
			  stream,
			  tempImage,
			  jpegImage,
			  jpegDigest);
			  
		return;
		
		}
	
	// Figure out inner and outer samples.
	
	uint32 innerSamples = 1;
	uint32 outerSamples = 1;
	
	if (ifd.fPlanarConfiguration == pcPlanar)
		{
		outerSamples = ifd.fSamplesPerPixel;
		}
	else
		{
		innerSamples = ifd.fSamplesPerPixel;
		}
	
	// Calculate number of tiles to read.
	
	uint32 tilesAcross = ifd.TilesAcross ();
	uint32 tilesDown   = ifd.TilesDown   ();
	
	uint32 tileCount = SafeUint32Mult (tilesAcross, tilesDown, outerSamples);
	
	// Find the tile offsets.
		
	dng_memory_data tileOffsetData (tileCount, sizeof (uint64));
	
	uint64 *tileOffset = tileOffsetData.Buffer_uint64 ();
	
	if (tileCount <= dng_ifd::kMaxTileInfo)
		{
		
		for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
			{
			
			tileOffset [tileIndex] = ifd.fTileOffset [tileIndex];
			
			}
		
		}
		
	else
		{
		
		stream.SetReadPosition (ifd.fTileOffsetsOffset);
		
		for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
			{
			
			tileOffset [tileIndex] = stream.TagValue_uint32 (ifd.fTileOffsetsType);
			
			}
		
		}
		
	// Quick validity check on tile offsets.
	
	for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
		{
		
		#if qDNGValidate
		
		if (tileOffset [tileIndex] < 8)
			{
			
			ReportWarning ("Tile/Strip offset less than 8");
			
			}
		
		#endif
		
		if (tileOffset [tileIndex] >= stream.Length ())
			{
			
			ThrowBadFormat ();
			
			}
		
		}
		
	// Buffer to hold the tile byte counts, if needed.
	
	dng_memory_data tileByteCountData;
	
	uint32 *tileByteCount = NULL;
	
	// If we can compute the number of bytes needed to store the
	// data, we can split the read for each tile into sub-tiles.
		
	uint32 uncompressedSize = 0;

	uint32 subTileLength = ifd.fTileLength;
	
	if (ifd.TileByteCount (ifd.TileArea (0, 0)) != 0)
		{
		
		uint32 bytesPerPixel = TagTypeSize (ifd.PixelType ());
		
		uint32 bytesPerRow = SafeUint32Mult (ifd.fTileWidth, innerSamples,
											 bytesPerPixel);
				
		subTileLength = Pin_uint32 (ifd.fSubTileBlockRows,
									kImageBufferSize / bytesPerRow, 
									ifd.fTileLength);
									
		subTileLength = subTileLength / ifd.fSubTileBlockRows
									  * ifd.fSubTileBlockRows;
									  
		uncompressedSize = SafeUint32Mult (subTileLength, bytesPerRow);
									
		}
		
	// Else we need to know the byte counts.
	
	else
		{
		
		tileByteCountData.Allocate (tileCount, sizeof (uint32));
		
		tileByteCount = tileByteCountData.Buffer_uint32 ();
		
		if (tileCount <= dng_ifd::kMaxTileInfo)
			{
			
			for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
				{
				
				tileByteCount [tileIndex] = ifd.fTileByteCount [tileIndex];
				
				}
			
			}
			
		else
			{
			
			stream.SetReadPosition (ifd.fTileByteCountsOffset);
			
			for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
				{
				
				tileByteCount [tileIndex] = stream.TagValue_uint32 (ifd.fTileByteCountsType);
				
				}
			
			}
			
		// Quick validity check on tile byte counts.
		
		for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
			{
			
			if (tileByteCount [tileIndex] < 1 ||
				tileByteCount [tileIndex] > stream.Length ())
				{
				
				ThrowBadFormat ();
				
				}
			
			}
		
		}
		
	// Find maximum tile size, if possible.
		
	uint32 maxTileByteCount = 0;
	
	if (tileByteCount)
		{
		
		for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
			{
			
			maxTileByteCount = Max_uint32 (maxTileByteCount,
										   tileByteCount [tileIndex]);
										   
			}
			
		}
		
	// Do we need a compressed data buffer?

	uint32 compressedSize = 0;
	
	bool needsCompressedBuffer = NeedsCompressedBuffer (ifd);
	
	if (needsCompressedBuffer)
		{
		
		if (!tileByteCount)
			{
			ThrowBadFormat ();
			}
		
		compressedSize = maxTileByteCount;
		
		}
		
	// Are we keeping the compressed JPEG image data?
	
	if (jpegImage)
		{
		
		if (ifd.IsBaselineJPEG ())
			{
			
			jpegImage->fImageSize.h = ifd.fImageWidth;
			jpegImage->fImageSize.v = ifd.fImageLength;
			
			jpegImage->fTileSize.h = ifd.fTileWidth;
			jpegImage->fTileSize.v = ifd.fTileLength;
			
			jpegImage->fUsesStrips = ifd.fUsesStrips;
			
			jpegImage->fJPEGData.Reset (tileCount);
						
			}
			
		else
			{
			
			jpegImage = NULL;
			
			}
		
		}
		
	// Do we need to read the JPEG tables?
	
	if (ifd.fJPEGTablesOffset && ifd.fJPEGTablesCount)
		{
	
		if (ifd.IsBaselineJPEG ())
			{
			
			fJPEGTables.Reset (host.Allocate (ifd.fJPEGTablesCount));
			
			stream.SetReadPosition (ifd.fJPEGTablesOffset);
			
			stream.Get (fJPEGTables->Buffer      (),
						fJPEGTables->LogicalSize ());
			
			}
			
		}
		
	AutoArray<dng_fingerprint> jpegTileDigest;
	
	if (jpegDigest)
		{
		
		jpegTileDigest.Reset (
			SafeUint32Add(tileCount, (fJPEGTables.Get () ? 1 : 0)));
		
		}
			
	// Don't read planes we are not actually saving.
	
	outerSamples = Min_uint32 (image.Planes (), outerSamples);
		
	// See if we can do this read using multiple threads.
	
	bool useMultipleThreads = (outerSamples * tilesDown * tilesAcross >= 2) &&
							  (host.PerformAreaTaskThreads () > 1) &&
							  (maxTileByteCount > 0 && maxTileByteCount <= 1024 * 1024) &&
							  (subTileLength == ifd.fTileLength) &&
							  (ifd.fCompression != ccUncompressed);
	
#if qImagecore
	useMultipleThreads = false;	
#endif
		
	if (useMultipleThreads)
		{
		
		uint32 threadCount = Min_uint32 (outerSamples * tilesDown * tilesAcross,
										 host.PerformAreaTaskThreads ());
		
		dng_read_tiles_task task (*this,
								  host,
								  ifd,
								  stream,
								  image,
								  jpegImage,
								  jpegTileDigest.Get (),
								  outerSamples,
								  innerSamples,
								  tilesDown,
								  tilesAcross,
								  tileOffset,
								  tileByteCount,
								  maxTileByteCount,
								  uncompressedSize);
								  
		host.PerformAreaTask (task,
							  dng_rect (0, 0, 16, 16 * threadCount));
		
		}
		
	// Else use a single thread to read all the tiles.
	
	else
		{
		
		AutoPtr<dng_memory_block> compressedBuffer;
		AutoPtr<dng_memory_block> uncompressedBuffer;
		AutoPtr<dng_memory_block> subTileBlockBuffer;
		
		if (uncompressedSize)
			{
			uncompressedBuffer.Reset (host.Allocate (uncompressedSize));
			}
			
		if (compressedSize && !jpegImage)
			{
			compressedBuffer.Reset (host.Allocate (compressedSize));
			}
			
		else if (jpegDigest)
			{
			compressedBuffer.Reset (host.Allocate (maxTileByteCount));
			}
			
		tileIndex = 0;
		
		for (uint32 plane = 0; plane < outerSamples; plane++)
			{
			
			for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
				{
				
				for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
					{
					
					stream.SetReadPosition (tileOffset [tileIndex]);
					
					dng_rect tileArea = ifd.TileArea (rowIndex, colIndex);
					
					uint32 subTileCount = (tileArea.H () + subTileLength - 1) /
										  subTileLength;
										  
					for (uint32 subIndex = 0; subIndex < subTileCount; subIndex++)
						{
						
						host.SniffForAbort ();
					
						dng_rect subArea (tileArea);
						
						subArea.t = tileArea.t + subIndex * subTileLength;
						
						subArea.b = Min_int32 (subArea.t + subTileLength,
											   tileArea.b);
						
						uint32 subByteCount;
						
						if (tileByteCount)
							{
							subByteCount = tileByteCount [tileIndex];
							}
						else
							{
							subByteCount = ifd.TileByteCount (subArea);
							}
							
						if (jpegImage)
							{
							
							jpegImage->fJPEGData [tileIndex].Reset (host.Allocate (subByteCount));
							
							stream.Get (jpegImage->fJPEGData [tileIndex]->Buffer (), subByteCount);
							
							stream.SetReadPosition (tileOffset [tileIndex]);
							
							}
							
						else if ((needsCompressedBuffer || jpegDigest) && subByteCount)
							{
							
							stream.Get (compressedBuffer->Buffer (), subByteCount);
							
							if (jpegDigest)
								{
								
								dng_md5_printer printer;
								
								printer.Process (compressedBuffer->Buffer (),
												 subByteCount);
												 
								jpegTileDigest [tileIndex] = printer.Result ();
								
								}
							
							}
							
						ReadTile (host,
								  ifd,
								  stream,
								  image,
								  subArea,
								  plane,
								  innerSamples,
								  subByteCount,
								  jpegImage ? jpegImage->fJPEGData [tileIndex] : compressedBuffer,
								  uncompressedBuffer,
								  subTileBlockBuffer);
								  
						}
					
					tileIndex++;
					
					}
					
				}

			}
			
		}
		
	// Finish up JPEG digest computation, if needed.
	
	if (jpegDigest)
		{
		
		if (fJPEGTables.Get ())
			{
			
			dng_md5_printer printer;
			
			printer.Process (fJPEGTables->Buffer      (),
							 fJPEGTables->LogicalSize ());
							 
			jpegTileDigest [tileCount] = printer.Result ();
			
			}
			
		dng_md5_printer printer2;
		
		for (uint32 j = 0; j < tileCount + (fJPEGTables.Get () ? 1 : 0); j++)
			{
			
			printer2.Process (jpegTileDigest [j].data,
							  dng_fingerprint::kDNGFingerprintSize);
							  
			}
			
		*jpegDigest = printer2.Result ();
		
		}
		
	// Keep the JPEG table in the jpeg image, if any.
	
	if (jpegImage)
		{
		
		jpegImage->fJPEGTables.Reset (fJPEGTables.Release ());
		
		}

	}

/*****************************************************************************/