/*****************************************************************************/
// Copyright 2006-2022 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.
/*****************************************************************************/

#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_globals.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_ifd.h"
#include "dng_jpeg_image.h"
#include "dng_jxl.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_uncopyable.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 dng_uncopyable
	{
	
	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);

	};

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

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 + 1) * sizeof (LZWExpanderNode));
	
	fTable = (LZWExpanderNode *) fBuffer.Buffer ();

	}

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

void dng_lzw_expander::InitTable ()
	{

	fCodeSize = 9;

	fNextCode = 258;
		
	LZWExpanderNode *node = &fTable [0];
	
	for (int32 code = 0; code <= kTableSize; 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)
	{

	if (sCount < 0 || dCount < 0)
		{
		return false;
		}
	
	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;

		(void) inChar;
		
		*(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;

				(void) dCount;
				
				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;
	
	}

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

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_uncopyable
	{
	
	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);
							
	};

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

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);

	DNG_REQUIRE (bytesPerRow > 0, "Bad bytesPerRow in dng_image_spooler");
	
	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,
									  bool /* usingMultipleThreads */)
	{
	
	#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 ("invalid cinfo.num_components");
				}
			
			// 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 ("JPEG dimensions do not match tile");
				}

			}
			
		// 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)
	{
	
	if (tileByteCount <= 2)
		{
		ThrowEndOfFile ();
		}
		
	uint32 tablesByteCount = tablesBlock ? tablesBlock->LogicalSize () : 0;
	
	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,
									   bool usingMultipleThreads)
	{
	
	// 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 (),
						 usingMultipleThreads);
		
		}
		
	else
		{
	
		if (ifd.fPatchFirstJPEGByte && tileByteCount)
			{
			jpegDataInMemory [0] = 0xFF;
			}

		DecodeLossyJPEG (host,
						 image,
						 tileArea,
						 plane,
						 planes,
						 ifd.fPhotometricInterpretation,
						 tileByteCount,
						 jpegDataInMemory,
						 usingMultipleThreads);
		
		}
				
	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;
		}

	dng_safe_uint32 bytesPerRow =
		(dng_safe_uint32 (tileArea.W ()) * planes *
		 static_cast<uint32> (sizeof (uint16)));
	
	uint32 rowsPerStrip = Pin_uint32 (ifd.fSubTileBlockRows,
									  kImageBufferSize / bytesPerRow.Get (),
									  tileArea.H ());
									  
	rowsPerStrip = rowsPerStrip / ifd.fSubTileBlockRows
								* ifd.fSubTileBlockRows;
									  
	dng_safe_uint32 bufferSize = bytesPerRow * rowsPerStrip;
	
	if (uncompressedBuffer.Get () &&
		uncompressedBuffer->LogicalSize () < bufferSize.Get ())
		{
		
		uncompressedBuffer.Reset ();
		
		}
		
	if (uncompressedBuffer.Get () == NULL)
		{
		
		uncompressedBuffer.Reset (host.Allocate (bufferSize.Get ()));
									
		}
	
	dng_image_spooler spooler (host,
							   ifd,
							   image,
							   tileArea,
							   plane,
							   planes,
							   *uncompressedBuffer.Get (),
							   subTileBlockBuffer);
			

				   
	dng_safe_uint32 decodedSize = (dng_safe_uint32 (tileArea.W ()) *
								   tileArea.H () *
								   planes * 
								   (uint32) sizeof (uint16));
							
	bool bug16 = ifd.fLosslessJPEGBug16;
	
	uint64 tileOffset = stream.Position ();

	DoDecodeLosslessJPEG (stream,
						  spooler,
						  decodedSize.Get (),
						  decodedSize.Get (),
						  bug16,
						  tileOffset + tileByteCount);

	return true;
	
	}

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

bool dng_read_image::ReadJXL (dng_host &host,
							  const dng_ifd &ifd,
							  dng_stream &stream,
							  dng_image &image,
							  const dng_rect &tileArea,
							  uint32 tileByteCount,
							  uint8 *jxlCompressedRawBitStream,
							  bool usingMultipleThreads)
	{

	(void) ifd;
	
	dng_jxl_decoder decoder;

	decoder.fNeedBoxMeta     = false;
	decoder.fNeedImage       = true;
	decoder.fUsePixelBuffer  = true;
	decoder.fUseSingleThread = usingMultipleThreads;
		
	if (!jxlCompressedRawBitStream)
		{
		
		decoder.Decode (host,
						stream);

		}

	else
		{
		
		dng_stream tempStream (jxlCompressedRawBitStream,
							   tileByteCount);

		decoder.Decode (host,
						tempStream);

		}

	// Check there is a result pixel buffer.

	DNG_REQUIRE (decoder.fMainPixelBuffer.Get (),
				 "missing fMainPixelBuffer in ReadJXL");

	auto &buffer = *decoder.fMainPixelBuffer;

	// Check that it has the same dimensions as the requested tile area.

	DNG_REQUIRE (buffer.fArea.Size () == tileArea.Size (),
				 "mismatch tile size in ReadJXL");

	// Check matching pixel type.

	DNG_REQUIRE (buffer.fPixelType == image.PixelType (),
				 "mismatch pixel type in ReadJXL");

	// Check matching plane count.

	DNG_REQUIRE (buffer.fPlanes == image.Planes (),
				 "mismatch plane count in ReadJXL");

	// Store in image.

	buffer.fArea = tileArea;

	image.Put (buffer);

	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 ccJXL:
			{

			if (ifd.fSamplesPerPixel != 1 &&
				ifd.fSamplesPerPixel != 3)
				{
				return false;
				}

			if (ifd.fSampleFormat [0] == sfUnsignedInteger)
				{
				
				return ifd.fBitsPerSample [0] >= 8 &&
					   ifd.fBitsPerSample [0] <= 16;
				
				}

			else
				{
				
				return ifd.fBitsPerSample [0] >= 16 &&
					   ifd.fBitsPerSample [0] <= 32;
				
				}
				
			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,
							   std::shared_ptr<dng_memory_block> &compressedBuffer,
							   AutoPtr<dng_memory_block> &uncompressedBuffer,
							   AutoPtr<dng_memory_block> &subTileBlockBuffer,
							   bool usingMultipleThreads)
	{
	
	switch (ifd.fCompression)
		{
		
		case ccLZW:
		case ccDeflate:
		case ccOldDeflate:
		case ccPackBits:
			{
			
			// Figure out uncompressed size.

			dng_safe_uint32 bytesPerSample = (ifd.fBitsPerSample [0] >> 3);
			
			dng_safe_uint32 sampleCount = (dng_safe_uint32 (planes)		   * 
										   dng_safe_uint32 (tileArea.W ()) * 
										   dng_safe_uint32 (tileArea.H ()));
			
			dng_safe_uint32 uncompressedSize = sampleCount * bytesPerSample;

			// 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 ();
				}

			dng_pixel_buffer buffer (tileArea, 
									 plane, 
									 planes, 
									 pixelType, 
									 pcInterleaved,
									 NULL);

			// Specify custom pixel size (e.g., ttFloat pixel type but
			// pixel size may be 2 for stored 16-bit half-float).

			buffer.fPixelSize = bytesPerSample.Get ();

			dng_safe_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)
				{

				bufferSize += (dng_safe_uint32 (dng_safe_int32 (buffer.fRowStep)) * 
							   dng_safe_uint32 (buffer.fPixelSize));

				}
				
			// 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.Get (),
										 (sampleCount * 4u).Get ());
				}
			
			// 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.Get ())
				{
				
				uncompressedBuffer.Reset ();
				
				}
				
			if (uncompressedBuffer.Get () == NULL)
				{

				uncompressedBuffer.Reset (host.Allocate (bufferSize.Get ()));
											
				}
				
			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.Get ()))
					{
					ThrowBadFormat ();
					}
				
				}
				
			else if (ifd.fCompression == ccPackBits)
				{
				
				dng_stream subStream (compressedBuffer->Buffer_uint8 (),
									  tileByteCount);
											 
				if (!DecodePackBits (subStream,
									 (uint8 *) buffer.fData,
									 uncompressedSize.Get ()))
					{
					ThrowBadFormat ();
					}
				
				}
			
			else
				{
				
				uLongf dstLen = uncompressedSize.Get ();
							
				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.Get ())
					{
					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);
					uint8 *dstPtr = (uint8 *) buffer.DirtyPixel (row - 1, tileArea.l, plane);
					
					DecodeFPDelta (srcPtr,
								   dstPtr,
								   tileArea.W () / xFactor,
								   planes		 * xFactor,
								   bytesPerSample.Get ());
					
					}
				
				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.Get () - 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.Get () - 1) * 3;
				uint32 *dstPtr = ((uint32 *) buffer.fData) + (sampleCount.Get () - 1);
				
				if (stream.BigEndian () || ifd.fPredictor == cpFloatingPoint   ||
										   ifd.fPredictor == cpFloatingPointX2 ||
										   ifd.fPredictor == cpFloatingPointX4)
					{
				
					for (uint32 index = 0; index < sampleCount.Get (); index++)
						{
						
						*(dstPtr--) = DNG_FP24ToFloat (srcPtr);
						
						srcPtr -= 3;
						
						}
						
					}
					
				else
					{
					
					for (uint32 index = 0; index < sampleCount.Get (); 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 ? compressedBuffer->Buffer_uint8 () : NULL,
									  usingMultipleThreads))
					{
					
					return;
					
					}
				
				}
				
			else
				{
				
				// Otherwise it 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 ? compressedBuffer->Buffer_uint8 () : NULL,
								  usingMultipleThreads))
				{
				
				return;
				
				}
							
			break;
			
			}

		case ccJXL:
			{

			DNG_REQUIRE (plane == 0,
						 "Unexpected plane in ReadTile for ccJXL");

			DNG_REQUIRE (planes == 1 ||
						 planes == 3,
						 "Unexpected planes in ReadTile for ccJXL");
			
			if (ReadJXL (host,
						 ifd,
						 stream,
						 image,
						 tileArea,
						 tileByteCount,
						 compressedBuffer ? compressedBuffer->Buffer_uint8 () : NULL,
						 usingMultipleThreads))
				{
				
				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;
	
	}
	
/*****************************************************************************/

dng_read_tiles_task::dng_read_tiles_task (dng_read_image &readImage,
										  dng_host &host,
										  const dng_ifd &ifd,
										  dng_stream &stream,
										  dng_image &image,
										  dng_lossy_compressed_image *lossyImage,
										  dng_fingerprint *lossyTileDigest,
										  uint32 outerSamples,
										  uint32 innerSamples,
										  uint32 tilesDown,
										  uint32 tilesAcross,
										  uint64 *tileOffset,
										  uint32 *tileByteCount,
										  uint32 compressedSize,
										  uint32 uncompressedSize)

	:	dng_area_task ("dng_read_tiles_task")

	,	fReadImage		  (readImage)
	,	fHost			  (host)
	,	fIFD			  (ifd)
	,	fStream			  (stream)
	,	fImage			  (image)
	,	fLossyImage		  (lossyImage)
	,	fLossyTileDigest  (lossyTileDigest)
	,	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 dng_read_tiles_task::Process (uint32 /* threadIndex */,
								   const dng_rect & /* tile */,
								   dng_abort_sniffer *sniffer)
	{
			
	std::shared_ptr<dng_memory_block> compressedBuffer;
	
	AutoPtr<dng_memory_block> uncompressedBuffer;
	AutoPtr<dng_memory_block> subTileBlockBuffer;

	if (!fLossyImage)
		{
		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++;

			ReadTask (tileIndex,
					  byteCount,
					  compressedBuffer.get ());

			}

		ProcessTask (tileIndex,
					 byteCount,
					 sniffer,
					 compressedBuffer,
					 uncompressedBuffer,
					 subTileBlockBuffer);

		}

	}

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

void dng_read_tiles_task::ReadTask (uint32 tileIndex,
									uint32 &byteCount,
									dng_memory_block *compressedBuffer)
	{
	
	TempStreamSniffer noSniffer (fStream, NULL);

	fStream.SetReadPosition (fTileOffset [tileIndex]);

	byteCount = fTileByteCount [tileIndex];

	if (fLossyImage)
		{

		fLossyImage->fData [tileIndex] . reset (fHost.Allocate (byteCount));

		}

	fStream.Get (fLossyImage ? fLossyImage->fData [tileIndex]->Buffer ()
							 : compressedBuffer->Buffer (),
				 byteCount);	

	}

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

void dng_read_tiles_task::ProcessTask (uint32 tileIndex,
									   uint32 byteCount,
									   dng_abort_sniffer *sniffer,
									   std::shared_ptr<dng_memory_block> &compressedBuffer,
									   AutoPtr<dng_memory_block> &uncompressedBuffer,
									   AutoPtr<dng_memory_block> &subTileBlockBuffer)
	{
	
	dng_abort_sniffer::SniffForAbort (sniffer);

	if (fLossyTileDigest)
		{

		dng_md5_printer printer;

		printer.Process (compressedBuffer->Buffer (),
						 byteCount);

		fLossyTileDigest [tileIndex] = printer.Result ();

		}

	dng_stream tileStream (fLossyImage ? fLossyImage->fData [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,
						 fLossyImage ? fLossyImage->fData [tileIndex]
									 : compressedBuffer,
						 uncompressedBuffer,
						 subTileBlockBuffer,
						 true);

	}

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

class dng_interleave_task : public dng_area_task
						  , private dng_uncopyable
	{
	
	public:
	
		const dng_image &fSrcImage;
			  dng_image &fDstImage;
			  
		int32 fRowFactor = 1;
		int32 fColFactor = 1;
		
		bool fEncode = false;
		
	private:
	
		enum
			{
			kMaxThreads = 4
			};
		
		AutoPtr<dng_memory_block> fSrcBuffer [kMaxThreads];
		AutoPtr<dng_memory_block> fDstBuffer [kMaxThreads];
		
	public:
	
		dng_interleave_task (const dng_image &srcImage,
							 dng_image &dstImage,
							 const int32 rowFactor,
							 const int32 colFactor,
							 bool encode)
								  
			:	dng_area_task ("dng_interleave_task")
								  
			,	fSrcImage  (srcImage)
			,	fDstImage  (dstImage)
			,	fRowFactor (rowFactor)
			,	fColFactor (colFactor)
			,	fEncode    (encode)
			
			{
			
			if (fRowFactor >= (int32) fSrcImage.Bounds ().H ())
				{
				fRowFactor = 1;
				}
			
			if (fColFactor >= (int32) fSrcImage.Bounds ().W ())
				{
				fColFactor = 1;
				}
			
			fMaxThreads = kMaxThreads;
			
			fMaxTileSize = dng_point (512, 512);
			
			}
	
		dng_rect RepeatingTile1 () const override
			{
			return fDstImage.RepeatingTile ();
			}
			
		void Start (uint32 threadCount,
					const dng_rect &dstArea,
					const dng_point &tileSize,
					dng_memory_allocator *allocator,
					dng_abort_sniffer *sniffer) override;

		void Process (uint32 threadIndex,
					  const dng_rect &tile,
					  dng_abort_sniffer *sniffer) override;
		
	};

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

void dng_interleave_task::Start (uint32 threadCount,
								 const dng_rect & /* dstArea */,
								 const dng_point &tileSize,
								 dng_memory_allocator *allocator,
								 dng_abort_sniffer * /* sniffer */)
	{
	
	uint32 srcBufferSize = ((tileSize.h + fColFactor - 1) / fColFactor) *
						   ((tileSize.v + fRowFactor - 1) / fRowFactor) *
						   fDstImage.PixelSize () *
						   fDstImage.Planes ();
						   
	uint32 dstBufferSize = tileSize.h *
						   tileSize.v *
						   fDstImage.PixelSize () *
						   fDstImage.Planes ();
	
	for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++)
		{
		
		fSrcBuffer [threadIndex].Reset (allocator->Allocate (srcBufferSize));
		
		fDstBuffer [threadIndex].Reset (allocator->Allocate (dstBufferSize));
		
		}
	
	}

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

void dng_interleave_task::Process (uint32 threadIndex,
								   const dng_rect &tile,
								   dng_abort_sniffer * /* sniffer */)
	{
	
	dng_pixel_buffer dstBuffer;
	
	dstBuffer.fArea      = tile;
	dstBuffer.fPlane     = 0;
	dstBuffer.fPlanes    = fDstImage.Planes ();
	dstBuffer.fPlaneStep = 1;
	dstBuffer.fColStep   = dstBuffer.fPlaneStep * dstBuffer.fPlanes;
	dstBuffer.fRowStep   = dstBuffer.fColStep * dstBuffer.fArea.W ();
	dstBuffer.fPixelType = fDstImage.PixelType ();
	dstBuffer.fPixelSize = fDstImage.PixelSize ();
	dstBuffer.fData      = fDstBuffer [threadIndex]->Buffer ();
	dstBuffer.fDirty     = true;
	
	dng_pixel_buffer srcBuffer = dstBuffer;
	
	srcBuffer.fData = fSrcBuffer [threadIndex]->Buffer ();
	
	if (fEncode)
		{
		fSrcImage.Get (dstBuffer);
		}
		
	for (int32 rOffset = 0; rOffset < Min_int32 (fRowFactor, tile.H ()); rOffset++)
		{
		
		for (int32 cOffset = 0; cOffset < Min_int32 (fColFactor, tile.W ()); cOffset++)
			{
			
			int32 rField = (tile.t + rOffset) % fRowFactor;
			int32 cField = (tile.l + cOffset) % fColFactor;
			
			int32 rFieldOffset = rField * (fDstImage.Height () / fRowFactor) +
								 Min_int32 (rField, fDstImage.Height () % fRowFactor);
			
			int32 cFieldOffset = cField * (fDstImage.Width  () / fColFactor) +
								 Min_int32 (cField, fDstImage.Width  () % fColFactor);
								 
			srcBuffer.fArea.t = rFieldOffset + (tile.t + rOffset) / fRowFactor;
			srcBuffer.fArea.l = cFieldOffset + (tile.l + cOffset) / fColFactor;
			
			srcBuffer.fArea.b = srcBuffer.fArea.t + (tile.H () - rOffset + fRowFactor - 1) / fRowFactor;
			srcBuffer.fArea.r = srcBuffer.fArea.l + (tile.W () - cOffset + fColFactor - 1) / fColFactor;
			
			srcBuffer.fRowStep = srcBuffer.fColStep * srcBuffer.fArea.W ();
			
			if (!fEncode)
				{
				fSrcImage.Get (srcBuffer);
				}
				
			dng_pixel_buffer tmpBuffer = dstBuffer;
			
			tmpBuffer.fArea = srcBuffer.fArea;

			tmpBuffer.fData = dstBuffer.DirtyPixel (tile.t + rOffset,
													tile.l + cOffset);
													
			tmpBuffer.fRowStep *= fRowFactor;
			tmpBuffer.fColStep *= fColFactor;
			
			if (fEncode)
				{
				srcBuffer.CopyArea (tmpBuffer,
									tmpBuffer.fArea,
									tmpBuffer.fPlane,
									tmpBuffer.fPlanes);
				}
			else
				{
				tmpBuffer.CopyArea (srcBuffer,
									srcBuffer.fArea,
									srcBuffer.fPlane,
									srcBuffer.fPlanes);
				}
			
			if (fEncode)
				{
				fDstImage.Put (srcBuffer);
				}
			
			}
		
		}
		
	if (!fEncode)
		{
		fDstImage.Put (dstBuffer);
		}
	
	}
	
/*****************************************************************************/

void Interleave2D (dng_host &host,
				   const dng_image &srcImage,
				   dng_image &dstImage,
				   const int32 rowFactor,
				   const int32 colFactor,
				   bool encode)
	{
	
	#if qDNGValidate
	dng_timer timer ("Interleave2D");
	#endif
	
	DNG_REQUIRE (srcImage.Bounds    () == dstImage.Bounds    () &&
				 srcImage.Planes    () == dstImage.Planes    () &&
				 srcImage.PixelType () == dstImage.PixelType (),
				 "Mismatched src and dst in Interleave2D");
				 
	dng_interleave_task task (srcImage,
							  dstImage,
							  rowFactor,
							  colFactor,
							  encode);
								   
	host.PerformAreaTask (task, dstImage.Bounds ());
	
	}

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

void dng_read_image::Read (dng_host &host,
						   const dng_ifd &ifd,
						   dng_stream &stream,
						   dng_image &image,
						   dng_lossy_compressed_image *lossyImage,
						   dng_fingerprint *lossyDigest)
	{
	
	// Reject images that are too big.

	// Allow up to 2 * kMaxImageSide to deal with intermediate image objects
	// (e.g., rotated and padded).

	static const uint32 kLimit = 2 * kMaxImageSide;

	if (ifd.fImageWidth	 > kLimit ||
		ifd.fImageLength > kLimit)
		{
		
		ThrowBadFormat ("dng_read_image::Read image too large");
		
		}
	
	uint32 tileIndex;

	// Deal with both images that have row or column interleaving.

	if (ifd.fRowInterleaveFactor	> 1	||
		ifd.fColumnInterleaveFactor > 1)
		{

		// First, read into a full interleaved temporary image.
		
		AutoPtr<dng_image> tempImage (host.Make_dng_image (image.Bounds (),
														   image.Planes (),
														   image.PixelType ()));
		
		dng_ifd tempIFD (ifd);
		
		tempIFD.fRowInterleaveFactor    = 1;
		tempIFD.fColumnInterleaveFactor = 1;

		Read (host,
			  tempIFD,
			  stream,
			  *tempImage,
			  lossyImage,
			  lossyDigest);

		// Interleave both rows and columns.
		
		Interleave2D (host,
					  *tempImage,
					  image,
					  ifd.fRowInterleaveFactor,
					  ifd.fColumnInterleaveFactor,
					  false);
						
		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_uint64 (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++)
				{
				
				if (ifd.fTileByteCount [tileIndex] > 0xFFFFFFFF)
					{
					ThrowBadFormat ();
					}
				
				tileByteCount [tileIndex] = (uint32) ifd.fTileByteCount [tileIndex];
				
				}
			
			}
			
		else
			{
			
			stream.SetReadPosition (ifd.fTileByteCountsOffset);
			
			for (tileIndex = 0; tileIndex < tileCount; tileIndex++)
				{
				
				uint64 tileByteCount64 = stream.TagValue_uint64 (ifd.fTileByteCountsType);
				
				if (tileByteCount64 > 0xFFFFFFFF)
					{
					ThrowBadFormat ();
					}
				
				tileByteCount [tileIndex] = (uint32) tileByteCount64;
				
				}
			
			}
			
		// 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 lossy compressed image data?
	
	if (lossyImage)
		{
		
		if (ifd.IsBaselineJPEG () ||
			(ifd.fCompression == ccJXL))
			{
			
			lossyImage->fImageSize.h = ifd.fImageWidth;
			lossyImage->fImageSize.v = ifd.fImageLength;
			
			lossyImage->fTileSize.h = ifd.fTileWidth;
			lossyImage->fTileSize.v = ifd.fTileLength;
			
			lossyImage->fUsesStrips = ifd.fUsesStrips;

			lossyImage->fData.resize (tileCount);
						
			}

		else
			{
			
			lossyImage = 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 ());
			
			}
			
		}
		
	std::vector<dng_fingerprint> lossyTileDigests;
	
	if (lossyDigest)
		lossyTileDigests.resize (tileCount);
			
	// Don't read planes we are not actually saving.
	
	outerSamples = Min_uint32 (image.Planes (), outerSamples);
		
	// Performance optimization. We are reading in a potentially large image,
	// which is usually in a fairly contiguous byte range in the file. See if
	// it makes sense to increase the stream buffer size for this operation.
	
	uint64 contiguousByteCount = 0;
	
		{

		bool tilesInOrder = true;
		
		uint64 totalTileBytes = 0;
		
		uint64 minFileOffset = tileOffset [0];
		uint64 maxFileOffset = tileOffset [0];
		
		tileIndex = 0;
		
		for (uint32 plane = 0; plane < outerSamples; plane++)
			{
			
			for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
				{
				
				for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
					{
					
					uint64 thisOffset = tileOffset [tileIndex];
					
					uint64 thisByteCount;
					
					if (tileByteCount)
						{
						
						thisByteCount = tileByteCount [tileIndex];
						
						}
						
					else
						{
						
						thisByteCount = ifd.TileByteCount (ifd.TileArea (rowIndex, colIndex));
						
						}
						
					if (thisOffset < maxFileOffset)
						{
						tilesInOrder = false;
						}
						
					totalTileBytes += thisByteCount;
					
					minFileOffset = Min_uint64 (minFileOffset, thisOffset);
					maxFileOffset = Max_uint64 (maxFileOffset, thisOffset + thisByteCount);
					
					tileIndex++;
					
					}
					
				}
				
			}
			
		// Quick check for enough data in file.
		
		if (maxFileOffset > stream.Length ())
			{
			
			ThrowBadFormat ();
			
			}
			
		// Are all the tiles in order?
			
		if (tilesInOrder)
			{
			
			// And are going to read at least 90% of the bytes in the range?
			
			uint64 totalFileBytes = maxFileOffset - minFileOffset;
			
			if (totalTileBytes >= totalFileBytes * 9 / 10)
				{
				
				contiguousByteCount = totalFileBytes;
				
				}
		
			}
			
		}
		
	dng_stream_contiguous_read_hint readHint (stream,
											  host.Allocator (),
											  tileOffset [0],
											  contiguousByteCount);
		
	// 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 (useMultipleThreads)
		{

		DoReadTiles (host,
					 ifd,
					 stream,
					 image,
					 lossyImage,
					 lossyDigest ? &lossyTileDigests [0] : nullptr,
					 outerSamples,
					 innerSamples,
					 tilesDown,
					 tilesAcross,
					 tileOffset,
					 tileByteCount,
					 maxTileByteCount,
					 uncompressedSize);
		
		}
		
	// Else use a single thread to read all the tiles.
	
	else
		{
		
		std::shared_ptr<dng_memory_block> compressedBuffer;
		AutoPtr<dng_memory_block> uncompressedBuffer;
		AutoPtr<dng_memory_block> subTileBlockBuffer;
		
		if (uncompressedSize)
			{
			uncompressedBuffer.Reset (host.Allocate (uncompressedSize));
			}
			
		if (compressedSize && !lossyImage)
			{
			compressedBuffer.reset (host.Allocate (compressedSize));
			}
			
		else if (lossyDigest)
			{
			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 (lossyImage)
							{
							
							lossyImage->fData [tileIndex].reset (host.Allocate (subByteCount));
							
							stream.Get (lossyImage->fData [tileIndex]->Buffer (), subByteCount);
							
							stream.SetReadPosition (tileOffset [tileIndex]);
							
							}
							
						else if ((needsCompressedBuffer || lossyDigest) && subByteCount)
							{
							
							stream.Get (compressedBuffer->Buffer (), subByteCount);
							
							if (lossyDigest)
								{
								
								dng_md5_printer printer;
								
								printer.Process (compressedBuffer->Buffer (),
												 subByteCount);
												 
								lossyTileDigests [tileIndex] = printer.Result ();
								
								}
							
							}
							
						ReadTile (host,
								  ifd,
								  stream,
								  image,
								  subArea,
								  plane,
								  innerSamples,
								  subByteCount,
								  lossyImage ? lossyImage->fData [tileIndex] : compressedBuffer,
								  uncompressedBuffer,
								  subTileBlockBuffer,
								  useMultipleThreads);
								  
						}
					
					tileIndex++;
					
					}
					
				}

			}
			
		}
		
	// Finish up JPEG digest computation, if needed.
	
	if (lossyDigest)
		{

		if (fJPEGTables.Get ())
			{
			
			dng_md5_printer printer;
			
			printer.Process (fJPEGTables->Buffer	  (),
							 fJPEGTables->LogicalSize ());
							 
			lossyTileDigests.push_back (printer.Result ());
			
			}
			
		dng_md5_printer printer2;

		for (const auto &digest : lossyTileDigests)
			printer2.Process (digest.data,
							  uint32 (sizeof (digest.data)));
							  
		*lossyDigest = printer2.Result ();
		
		}
		
	// Keep the JPEG table in the jpeg image, if any.
	
	if (lossyImage && fJPEGTables.Get ())
		((dng_jpeg_image *) lossyImage)->fJPEGTables.Reset (fJPEGTables.Release ());	

	}

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

void dng_read_image::DoReadTiles (dng_host &host,
								  const dng_ifd &ifd,
								  dng_stream &stream,
								  dng_image &image,
								  dng_lossy_compressed_image *lossyImage,
								  dng_fingerprint *lossyTileDigest,
								  uint32 outerSamples,
								  uint32 innerSamples,
								  uint32 tilesDown,
								  uint32 tilesAcross,
								  uint64 *tileOffset,
								  uint32 *tileByteCount,
								  uint32 compressedSize,
								  uint32 uncompressedSize)
	{
	
	uint32 threadCount = Min_uint32 (outerSamples * tilesDown * tilesAcross,
									 host.PerformAreaTaskThreads ());

	dng_read_tiles_task task (*this,
							  host,
							  ifd,
							  stream,
							  image,
							  lossyImage,
							  lossyTileDigest,
							  outerSamples,
							  innerSamples,
							  tilesDown,
							  tilesAcross,
							  tileOffset,
							  tileByteCount,
							  compressedSize,
							  uncompressedSize);

	host.PerformAreaTask (task,
						  dng_rect (0, 0, 16, 16 * threadCount));
	
	}

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