/*****************************************************************************/
// Copyright 2006-2023 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_utils.h"

#include "dng_area_task.h"
#include "dng_assertions.h"
#include "dng_bottlenecks.h"
#include "dng_flags.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_image_writer.h"
#include "dng_memory_stream.h"
#include "dng_mutex.h"
#include "dng_point.h"
#include "dng_rect.h"
#include "dng_simd_type.h"
#include "dng_tag_codes.h"
#include "dng_tag_values.h"
#include "dng_tile_iterator.h"

#if qMacOS
#include <CoreServices/CoreServices.h>
#endif

#if qiPhone || qMacOS
// these provide timers
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif

#if qiPhone || qLinux
#include <signal.h> // for raise
#endif

#if qWinOS
#include <windows.h>
#else
#include <sys/time.h>
#include <stdarg.h> // for va_start/va_end
#endif

#if defined(__EMSCRIPTEN__)
#include "emscripten.h"
#endif

#include <atomic>

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

#if qDNGDebug

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

#if qMacOS
	#if qARM
		#define DNG_DEBUG_BREAK do { } while (0)
	#else
		#define DNG_DEBUG_BREAK __asm__ volatile ("int3")
	#endif
#elif qiPhone
	#if qiPhoneSimulator
		// simulator is either on Intel or arm64
		#if qARM
			#define DNG_DEBUG_BREAK do { } while (0)
		#else
			#define DNG_DEBUG_BREAK __asm__ volatile ("int3")
		#endif
	#else
		// You'll be one level deeper in __kill. Works on Linux, Android too.
		#define DNG_DEBUG_BREAK raise(SIGTRAP)
	#endif
#elif qWinOS
	// DebugBreak has to be emulated on WinRT
	#define DNG_DEBUG_BREAK DebugBreak()  
#elif qAndroid
	#define DNG_DEBUG_BREAK raise(SIGTRAP)
#elif qLinux
	#define DNG_DEBUG_BREAK raise(SIGTRAP)
#else
	#define DNG_DEBUG_BREAK
#endif

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

#endif	// qDNGDebug

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

#if qWinOS

void dng_outputdebugstring (const char *s,
							const char *nl)
	{

	static const bool sDebuggerPresent (IsDebuggerPresent () != 0);

	if (sDebuggerPresent)
		{
		OutputDebugStringA (s);
		if (nl && nl [0])
			{
			OutputDebugStringA (nl);
			}
		}

	}

#endif

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

#if defined(__EMSCRIPTEN__)

void dng_emscripten_log (int emLogType,
						 const char *s)
	{
	
	#if qDNGDebug
	emLogType |= EM_LOG_CONSOLE;
	#endif

	emscripten_log (emLogType,"%s", s);
	
	}

#endif

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

void dng_show_message (const char *s)
	{
	// only append a newline if there isn't already one
	const char* nl = "\n";
	if (s[0] && (s[strlen(s)-1] == '\n'))
		nl = "";
		
	#if qDNGPrintMessages
	
	// display the message
	if (gPrintAsserts)
		fprintf (stderr, "%s%s", s, nl);
		
	#elif qiPhone || qAndroid || qLinux || qWeb
	
	if (gPrintAsserts)
		fprintf (stderr, "%s%s", s, nl);

	#if qDNGDebug
	
	// iOS doesn't print a message to the console like DebugStr and MessageBox do, so we have to do both
	// You'll have to advance the program counter manually past this statement
	if (gBreakOnAsserts)
		DNG_DEBUG_BREAK;
	
	#endif	// qDNGDebug
	
	#elif qMacOS
	
	if (gBreakOnAsserts)
		{
		// truncate the to 255 chars
		char ss [256];
	
		uint32 len = (uint32) strlen (s);
		if (len > 255)
			len = 255;
		strncpy (&(ss [1]), s, len );
		ss [0] = (unsigned char) len;
	
		DebugStr ((unsigned char *) ss);
		}
	 else if (gPrintAsserts)
		{
		// For macOS have non-breaking assert emit to stdout
		// rather than stderr so that message will appear
		// in the Xcode console window.
		//fprintf(stderr, "%s%s", s, nl);
		fprintf (stdout, "%s%s", s, nl);
		}
	
	#elif qWinOS
	
	// display a dialog
	// This is not thread safe.	 Multiple message boxes can be launched.
	// Should also be launched in its own thread so main msg queue isn't thrown off.
	if (gBreakOnAsserts)
		{
		MessageBoxA (NULL, (LPSTR) s, NULL, MB_OK);
		}
	else if (gPrintAsserts)
		{
		fprintf (stderr, "%s%s", s, nl);

		// Emit assert message to MSVS Output window when debugging.
		dng_outputdebugstring (s, nl);
		}

	#endif

	}

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

void dng_show_message_f (const char *fmt, ... )
	{
	
	char buffer [2048];
	
	va_list ap;
	va_start (ap, fmt);

	vsnprintf (buffer, sizeof (buffer), fmt, ap);
	
	va_end (ap);
	
	dng_show_message (buffer);
	
	}

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

uint32 ComputeBufferSize (uint32 pixelType, 
						  const dng_point &tileSize,
						  uint32 numPlanes, 
						  PaddingType paddingType)
	{

	// Convert tile size to uint32.

	if (tileSize.h < 0 || tileSize.v < 0)
		{
		ThrowMemoryFull ("Negative tile size");
		}

	const uint32 tileSizeH = static_cast<uint32> (tileSize.h);
	const uint32 tileSizeV = static_cast<uint32> (tileSize.v);
	
	const uint32 pixelSize = TagTypeSize (pixelType);
	
	// Add padding to width if necessary.

	uint32 paddedWidth = tileSizeH;

	if (paddingType == padSIMDBytes)
		{

		if (!RoundUpForPixelSize (paddedWidth, 
								  pixelSize, 
								  &paddedWidth))
			{
			ThrowOverflow ("Arithmetic overflow computing buffer size");
			}

		}
	
	// Compute buffer size.

	uint32 bufferSize;

	if (!SafeUint32Mult (paddedWidth, tileSizeV, &bufferSize) ||
		!SafeUint32Mult (bufferSize,  pixelSize, &bufferSize) ||
		!SafeUint32Mult (bufferSize,  numPlanes, &bufferSize))
		{
		ThrowOverflow ("Arithmetic overflow computing buffer size");
		}
	
	return bufferSize;

	}

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

real64 TickTimeInSeconds ()
	{
	
	#if qWinOS
	
	// One might think it prudent to cache the frequency here, however
	// low-power CPU modes can, and do, change the value returned.
	// Thus the frequencey needs to be retrieved each time.
	
	// Note that the frequency changing can cause the return
	// result to jump backwards, which is why the TickCountInSeconds
	// (below) also exists.
		
	// Just plug in laptop when doing timings to minimize this.
	//	QPC/QPH is a slow call compared to rtdsc.
	//	but QPC/QPF is not tied to speed step, it's the northbridge timer.
	//	caching the invFrequency also avoids a costly divide
		
	static real64 freqMultiplier = 0.0;

	if (freqMultiplier == 0.0)
		{

		LARGE_INTEGER freq;

		QueryPerformanceFrequency (&freq);

		freqMultiplier = 1.0 / (real64) freq.QuadPart;

		}

	LARGE_INTEGER cycles;

	QueryPerformanceCounter (&cycles);

	return (real64) cycles.QuadPart * freqMultiplier;

	#elif qiPhone || qMacOS
	
	// cache frequency of high-perf timer
	static real64 freqMultiplier = 0.0;
	if (freqMultiplier == 0.0)
		{
			
		mach_timebase_info_data_t freq; 
		mach_timebase_info(&freq);
		
		// converts from nanos to micros
		//	numer = 125, denom = 3 * 1000
		freqMultiplier = ((real64)freq.numer / (real64)freq.denom) * 1.0e-9;
		
		}
	
	return mach_absolute_time() * freqMultiplier;
		
	#elif qAndroid || qLinux || qWeb

	//this is a fast timer to nanos
	struct timespec now;
	clock_gettime(CLOCK_MONOTONIC, &now);
	return now.tv_sec + (real64)now.tv_nsec * 1.0e-9;

	#else

	// Perhaps a better call exists. (e.g. avoid adjtime effects)

	struct timeval tv;
	
	gettimeofday (&tv, NULL);

	return tv.tv_sec + (real64)tv.tv_usec * 1.0e-6;
	
	#endif

	}

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

real64 TickCountInSeconds ()
	{
		
	return TickTimeInSeconds ();
	
	}

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

static std::atomic_int sTimerLevel (0);

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

void DNGIncrementTimerLevel ()
	{
	
	// This isn't thread coherent, multiple threads can create/destroy cr_timer
	//	 causing the tabbing to be invalid.	 Imagecore disables this.
	
	if (!gImagecore)
		{
		
		sTimerLevel++;
		
		}
		
	}

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

int32 DNGDecrementTimerLevel ()
	{

	if (gImagecore)
		{
		
		return 0;
		
		}
		
	else
		{
		
		return (int32) (--sTimerLevel);
		
		}
		
	}

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

dng_timer::dng_timer (const char *message)

	:	fMessage   (message				)
	,	fStartTime (TickTimeInSeconds ())
	
	{

	DNGIncrementTimerLevel ();
	
	}

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

dng_timer::~dng_timer ()
	{
	
	uint32 level = Pin_int32 (0, DNGDecrementTimerLevel (), 10);
	
	if (!gDNGShowTimers)
		return;

	real64 totalTime = TickTimeInSeconds () - fStartTime;
	
	#if defined(qCRLogging) && qCRLogging && defined(cr_logi)
		
	if (gImagecore)
		{
		// Imagecore force includes cr_log and overrides DNG to go to its logging under a mutex.
		// don't use indenting or fprintf to stderr, want these buffered
		cr_logi("timer", "%s: %0.3f sec\n", fMessage, totalTime);
		return;
		}
		
	#endif
		
	fprintf (stderr, "%*s%s: %0.3f sec\n", level*2, "", fMessage, totalTime);
	
	}

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

real64 MaxSquaredDistancePointToRect (const dng_point_real64 &point,
									  const dng_rect_real64 &rect)
	{
	
	real64 distSqr = DistanceSquared (point, 
									  rect.TL ());

	distSqr = Max_real64 (distSqr,
						  DistanceSquared (point, 
										   rect.BL ()));

	distSqr = Max_real64 (distSqr,
						  DistanceSquared (point, 
										   rect.BR ()));

	distSqr = Max_real64 (distSqr,
						  DistanceSquared (point, 
										   rect.TR ()));

	return distSqr;
	
	}

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

real64 MaxDistancePointToRect (const dng_point_real64 &point,
							   const dng_rect_real64 &rect)
	{

	return sqrt (MaxSquaredDistancePointToRect (point, 
												rect));

	}

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

dng_dither::dng_dither ()

	:	fNoiseBuffer ()

	{
	
	const uint32 kSeed = 1;

	fNoiseBuffer.Allocate (kRNGSize2D * sizeof (uint16));

	uint16 *buffer = fNoiseBuffer.Buffer_uint16 ();
		
	uint32 seed = kSeed;

	for (uint32 i = 0; i < kRNGSize2D; i++)
		{
		
		// The correct math for 16 to 8-bit dither would be:
		//
		// y = (x * 255 + r) / 65535;  (0 <= r <= 65534)
		//
		// The bottlnecks are using a faster approximation of
		// this math (using a power of two for the division):
		//
		// y = (x * 255 + r) / 65536;  (255 <= r <= 65535)
		//
		// To insure that all exact 8 bit values in 16 bit space
		// round trip exactly to the same 8-bit, we need to limit
		// r values to the range 255 to 65535.
		//
		// This results in the dither effect being slightly
		// imperfect, but correct round-tripping of 8-bit values
		// is far more important.
		
		uint16 value;
		
		do
			{
			
			seed = DNG_Random (seed);
			
			value = (uint16) seed;
			
			}
		while (value < 255);

		buffer [i] = value;

		}
	
	}

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

const dng_dither & dng_dither::Get ()
	{
	
	static dng_dither dither;
	
	return dither;
	
	}

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

void HistogramArea (dng_host & /* host */,
					const dng_image &image,
					const dng_rect &area,
					uint32 *hist,
					uint32 maxValue,
					uint32 plane)
	{
	
	DNG_ASSERT (image.PixelType () == ttShort, "Unsupported pixel type");
	
	DoZeroBytes (hist, (maxValue + 1) * (uint32) sizeof (uint32));
	
	dng_rect tile;
	
	dng_tile_iterator iter (image, area);
	
	while (iter.GetOneTile (tile))
		{
		
		dng_const_tile_buffer buffer (image, tile);
		
		const void *sPtr = buffer.ConstPixel (tile.t,
											  tile.l,
											  plane);
		
		uint32 count0 = 1;
		uint32 count1 = tile.H ();
		uint32 count2 = tile.W ();
		
		int32 step0 = 0;
		int32 step1 = buffer.fRowStep;
		int32 step2 = buffer.fColStep;
		
		OptimizeOrder (sPtr,
					   buffer.fPixelSize,
					   count0,
					   count1,
					   count2,
					   step0,
					   step1,
					   step2);
					   
		DNG_ASSERT (count0 == 1, "OptimizeOrder logic error");
		
		const uint16 *s1 = (const uint16 *) sPtr;
				
		for (uint32 row = 0; row < count1; row++)
			{
			
			if (maxValue == 0x0FFFF && step2 == 1)
				{
				
				for (uint32 col = 0; col < count2; col++)
					{
					
					uint32 x = s1 [col];
					
					hist [x] ++;
					
					}
					
				}
				
			else
				{
			
				const uint16 *s2 = s1;
			
				for (uint32 col = 0; col < count2; col++)
					{
					
					uint32 x = s2 [0];
					
					if (x <= maxValue)
						{
					
						hist [x] ++;
						
						}
					
					s2 += step2;
					
					}
					
				}
				
			s1 += step1;
			
			}
		
		}
		
	}
		
/*****************************************************************************/

template <SIMDType simd>
class dng_limit_float_depth_task: public dng_area_task
	{
	
	private:
	
		const dng_image &fSrcImage;
		
		dng_image &fDstImage;
		
		uint32 fBitDepth;
		
		real32 fScale;
			
	public:
	
		dng_limit_float_depth_task (const dng_image &srcImage,
									dng_image &dstImage,
									uint32 bitDepth,
									real32 scale);
							 
		virtual dng_rect RepeatingTile1 () const
			{
			return fSrcImage.RepeatingTile ();
			}
			
		virtual dng_rect RepeatingTile2 () const
			{
			return fDstImage.RepeatingTile ();
			}
							 
		virtual void Process (uint32 threadIndex,
							  const dng_rect &tile,
							  dng_abort_sniffer *sniffer);
								  
	};

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

template <SIMDType simd>
dng_limit_float_depth_task<simd>::dng_limit_float_depth_task
	(const dng_image &srcImage,
	 dng_image &dstImage,
	 uint32 bitDepth,
	 real32 scale)

	:	dng_area_task ("dng_limit_float_depth_task")
										
	,	fSrcImage (srcImage)
	,	fDstImage (dstImage)
	,	fBitDepth (bitDepth)
	,	fScale	  (scale)
	
	{
	
	}

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

template <SIMDType simd>

#ifdef __INTEL_LLVM_COMPILER
__attribute__((SET_CPU_FEATURE(simd)))
#endif // __INTEL_LLVM_COMPILER

void dng_limit_float_depth_task<simd>::Process (uint32 /* threadIndex */,
												const dng_rect &tile,
												dng_abort_sniffer * /* sniffer */)
	{

	INTEL_COMPILER_NEEDED_NOTE


#ifdef __INTEL_COMPILER
		SET_CPU_FEATURE(simd);
#endif // __INTEL_COMPILER


	dng_const_tile_buffer srcBuffer (fSrcImage, tile);
	dng_dirty_tile_buffer dstBuffer (fDstImage, tile);
	
	uint32 count0 = tile.H ();
	uint32 count1 = tile.W ();
	uint32 count2 = fDstImage.Planes ();
	
	int32 sStep0 = srcBuffer.fRowStep;
	int32 sStep1 = srcBuffer.fColStep;
	int32 sStep2 = srcBuffer.fPlaneStep;
	
	int32 dStep0 = dstBuffer.fRowStep;
	int32 dStep1 = dstBuffer.fColStep;
	int32 dStep2 = dstBuffer.fPlaneStep;

	const void *sPtr = srcBuffer.ConstPixel (tile.t,
											 tile.l,
											 0);
											 
		  void *dPtr = dstBuffer.DirtyPixel (tile.t,
											 tile.l,
											 0);

	OptimizeOrder (sPtr,
				   dPtr,
				   srcBuffer.fPixelSize,
				   dstBuffer.fPixelSize,
				   count0,
				   count1,
				   count2,
				   sStep0,
				   sStep1,
				   sStep2,
				   dStep0,
				   dStep1,
				   dStep2);
				   
	const real32 *sPtr0 = (const real32 *) sPtr;
		  real32 *dPtr0 = (		 real32 *) dPtr;
		  
	real32 scale = fScale;
		  
	bool limit16 = (fBitDepth == 16);
	bool limit24 = (fBitDepth == 24);
				   
	for (uint32 index0 = 0; index0 < count0; index0++)
		{
		
		const real32 *sPtr1 = sPtr0;
			  real32 *dPtr1 = dPtr0;
			  
		for (uint32 index1 = 0; index1 < count1; index1++)
			{
			
			// If the scale is a NOP, and the data is packed solid, we can just do memory
			// copy.
			
			if (scale == 1.0f && sStep2 == 1 && dStep2 == 1)
				{
				
				if (dPtr1 != sPtr1)			// srcImage != dstImage
					{
				
					memcpy (dPtr1, sPtr1, count2 * (uint32) sizeof (real32));
					
					}
				
				}
				
			else
				{
			
				const real32 *sPtr2 = sPtr1;
					  real32 *dPtr2 = dPtr1;
				INTEL_PRAGMA_SIMD_ASSERT_VECLEN_FLOAT(simd)
				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					real32 x = sPtr2 [0];
					
					x *= scale;
					
					dPtr2 [0] = x;
					
					sPtr2 += sStep2;
					dPtr2 += dStep2;
					
					}
					
				}
				
			// The data is now in the destination buffer.
				
			if (limit16)
				{

				//start by using intrinsic __m256__mm256_cvtph_ps_(__m128i_a)
				//once the intrinsic is written, merge this branch with previous one

				uint32 *dPtr2 = (uint32 *) dPtr1;

				INTEL_PRAGMA_SIMD_ASSERT_VECLEN_INT32(simd)

				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					uint32 x = dPtr2 [0];
					
					uint16 y = DNG_FloatToHalf (x);
					
					x = DNG_HalfToFloat (y);
											
					dPtr2 [0] = x;
					
					dPtr2 += dStep2;
					
					}
					
				}
				
			else if (limit24)
				{
			
				uint32 *dPtr2 = (uint32 *) dPtr1;
					  
				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					uint32 x = dPtr2 [0];
											
					uint8 temp [3];
					
					DNG_FloatToFP24 (x, temp);
					
					x = DNG_FP24ToFloat (temp);
					
					dPtr2 [0] = x;
					
					dPtr2 += dStep2;
					
					}
					
				}
			  
			sPtr1 += sStep1;
			dPtr1 += dStep1;
			
			}
				   
		sPtr0 += sStep0;
		dPtr0 += dStep0;
		
		}
					
	}

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

template <SIMDType simd>
void LimitFloatBitDepth (dng_host &host,
						 const dng_image &srcImage,
						 dng_image &dstImage,
						 uint32 bitDepth,
						 real32 scale)
	{
	
	DNG_ASSERT (srcImage.PixelType () == ttFloat, "Floating point image expected");
	DNG_ASSERT (dstImage.PixelType () == ttFloat, "Floating point image expected");
	
	dng_limit_float_depth_task<simd> task (srcImage,
										   dstImage,
										   bitDepth,
										   scale);
									 
	host.PerformAreaTask (task, dstImage.Bounds ());
	
	}

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

template
void LimitFloatBitDepth<Scalar> (dng_host &host,
								 const dng_image &srcImage,
								 dng_image &dstImage,
								 uint32 bitDepth,
								 real32 scale);

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

#if qDNGIntelCompiler

template
void LimitFloatBitDepth<AVX2> (dng_host &host,
							   const dng_image &srcImage,
							   dng_image &dstImage,
							   uint32 bitDepth,
							   real32 scale);

#endif	// qDNGIntelCompiler

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

void LimitFloatBitDepth (dng_host &host,
						 const dng_image &srcImage,
						 dng_image &dstImage,
						 uint32 bitDepth,
						 real32 scale)
	{
	
	// Kludge: Turning this off for now because the AVX2 path produces
	// slightly different results from the Scalar routine causing a mis-match
	// in raw digest values when building HDR merge result negatives which
	// causes the client to display a "file appears to be damaged" warning.
	// -bury 11/13/2017
	
	#if (qDNGIntelCompiler && qDNGExperimental && 0)

	if (gDNGMaxSIMD >= AVX2)
		{

		LimitFloatBitDepth<AVX2> (host,
								  srcImage,
								  dstImage,
								  bitDepth,
								  scale);

		}

	else

	#endif	// qDNGIntelCompiler && qDNGExperimental

		{

		LimitFloatBitDepth<Scalar> (host,
									srcImage,
									dstImage,
									bitDepth,
									scale);

		}

	}

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

uint32 MinBackwardVersionForCompression (uint32 compression)
	{
	
	if (compression == ccLossyJPEG)
		return dngVersion_1_4_0_0;

	if (compression == ccJXL)
		return dngVersion_1_7_0_0;

	return dngVersion_1_1_0_0;
	
	}

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

tiff_tag * dng_image_sequence_info::MakeTag (dng_memory_allocator &allocator) const
	{

	dng_memory_stream stream (allocator);

	TempBigEndian tempEndian (stream);

	// Write Sequence ID.

	if (fSequenceID.NotEmpty ())
		stream.Put (fSequenceID.Get (),
					fSequenceID.Length ());

	stream.PutZeros (1);

	// Write Sequence Type.

	if (fSequenceType.NotEmpty ())
		stream.Put (fSequenceType.Get (),
					fSequenceType.Length ());

	stream.PutZeros (1);

	// Write frame info.

	if (fFrameInfo.NotEmpty ())
		stream.Put (fFrameInfo.Get (),
					fFrameInfo.Length ());

	stream.PutZeros (1);

	// Write index, count, and final.

	stream.Put_uint32 (fIndex);
	stream.Put_uint32 (fCount);
	stream.Put_uint8 (fIsFinal);

	stream.SetReadPosition (0);

	const_dng_memory_block_sptr block (stream.AsMemoryBlock (allocator));

	AutoPtr<tag_owned_data_ptr> tag
		(new tag_owned_data_ptr (tcImageSequenceInfo,
								 ttUndefined,
								 block->LogicalSize (),
								 block));

	return tag.Release ();
	
	}

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

bool dng_image_stats::IsValidForPlaneCount (uint32 planeCount) const
	{
	
	DNG_REQUIRE (planeCount > 0, "Invalid plane count");

	if (fWeightedAverage.size () >= 2)
		return false;

	if (fWeights.size () != 0 &&
		fWeights.size () != size_t (planeCount))
		return false;
	
	if (fColorAverage.size () != 0 &&
		fColorAverage.size () != size_t (planeCount))
		return false;

	// Check weighted samples.
		
	if (!fWeightedSamples.empty ())
		{

		// Check too many samples.

		if (fWeightedSamples.size () > kMaxSamples)
			return false;
		
		// Check non-increasing order.

		real32 fPrev = fWeightedSamples.front ().fFrac;

		for (size_t i = 1; i < fWeightedSamples.size (); i++)
			{

			real32 fCurrent = fWeightedSamples [i].fFrac;
			
			if (fCurrent <= fPrev)
				return false;

			fPrev = fCurrent;

			}
		
		}

	// Check color samples.

	if (!fColorSamples.empty ())
		{

		// Check too many samples.

		if (fColorSamples.size () > kMaxSamples)
			return false;
		
		// Check non-increasing order and invalid plane count.

		real32 fPrev = fColorSamples.front ().fFrac;

		for (size_t i = 1; i < fColorSamples.size (); i++)
			{

			const auto &sample = fColorSamples [i];
			
			if (sample.fFrac <= fPrev)
				return false;

			fPrev = sample.fFrac;

			// Also check plane count.

			if (sample.fValues.size () != size_t (planeCount))
				return false;

			}
		
		}

	// Looks ok.

	return true;
	
	}

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

uint32 dng_image_stats::TagCount () const
	{

	uint32 count = 0;
	
	count += (!fWeightedAverage.empty () ? 1 : 0);
	count += (!fWeightedSamples.empty () ? 1 : 0);
	count += (!fWeights		   .empty () ? 1 : 0);
	count += (!fColorAverage   .empty () ? 1 : 0);
	count += (!fColorSamples   .empty () ? 1 : 0);

	return count;
	
	}

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

static void Put (dng_stream &stream,
				 uint32 tagCode,
				 const std::vector<real32> &values)
	{

	if (!values.empty ())
		{

		// Sanity check on values size.

		DNG_REQUIRE (values.size () <= 16,
					 "values vector too large");

		// Child tag code.

		stream.Put_uint32 (tagCode);

		// Byte length of child tag data.
		
		stream.Put_uint32 (uint32 (4 * values.size ()));

		// Child tag data.

		for (const auto x : values)
			stream.Put_real32 (x);
		
		}
	
	}

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

static void Put (dng_stream &stream,
				 uint32 tagCode,
				 const std::vector<dng_image_stats::weighted_sample> &samples)
	{

	if (!samples.empty ())
		{

		// Sanity check on samples size.

		DNG_REQUIRE (samples.size () <= dng_image_stats::kMaxSamples,
					 "samples vector too large");

		// Child tag code.

		stream.Put_uint32 (tagCode);

		// Byte length of child tag data.
		
		stream.Put_uint32 (uint32 (4 + 8 * samples.size ()));

		// Child tag data.

		// Write number of samples.

		stream.Put_uint32 (uint32 (samples.size ()));

		// Write data for each sample.

		for (const auto &sample : samples)
			{
			stream.Put_real32 (sample.fFrac);
			stream.Put_real32 (sample.fValue);
			}
		
		}
	
	}

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

static void Put (dng_stream &stream,
				 uint32 tagCode,
				 const std::vector<dng_image_stats::color_sample> &samples)
	{

	if (!samples.empty ())
		{

		// Sanity check on samples size.

		DNG_REQUIRE (samples.size () <= dng_image_stats::kMaxSamples,
					 "samples vector too large");

		// Child tag code.

		stream.Put_uint32 (tagCode);

		// Byte length of child tag data.

		uint32 bytes = 4;

		for (const auto &sample : samples)
			bytes += (4 + 4 * uint32 (sample.fValues.size ()));
		
		stream.Put_uint32 (bytes);

		// Child tag data.

		// Write number of samples.

		stream.Put_uint32 (uint32 (samples.size ()));

		// Write data for each sample.

		for (const auto &sample : samples)
			{
			
			stream.Put_real32 (sample.fFrac);
			
			for (const auto &x : sample.fValues)
				stream.Put_real32 (x);
			
			}
		
		}
	
	}

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

tiff_tag * dng_image_stats::MakeTag (dng_memory_allocator &allocator) const
	{
	
	dng_memory_stream stream (allocator);

	// Tag data is big-endian byte order.

	TempBigEndian tempEndian (stream);

	// Write the number of tags.

	uint32 count = TagCount ();

	stream.Put_uint32 (count);

	// Write child tag table.

	Put (stream, kTag_WeightedAverage , fWeightedAverage);
	Put (stream, kTag_WeightedSamples , fWeightedSamples);
	Put (stream, kTag_Weights		  , fWeights);
	Put (stream, kTag_ColorAverage	  , fColorAverage);
	Put (stream, kTag_ColorSamples	  , fColorSamples);
	
	// Make a tag from the data.

	stream.SetReadPosition (0);

	const_dng_memory_block_sptr block (stream.AsMemoryBlock (allocator));

	AutoPtr<tag_owned_data_ptr> tag
		(new tag_owned_data_ptr (tcImageStats,
								 ttUndefined,
								 block->LogicalSize (),
								 block));

	return tag.Release ();
	
	}

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

bool dng_image_stats::operator== (const dng_image_stats &src) const
	{
	
	return (fWeightedAverage   == src.fWeightedAverage &&
			fWeightedSamples   == src.fWeightedSamples &&
			fWeights		   == src.fWeights		   &&
			fColorAverage	   == src.fColorAverage	   &&
			fColorSamples	   == src.fColorSamples);
	
	}

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

void dng_image_stats::Parse (dng_stream &stream)
	{
	
	// Tag data is big-endian byte order.

	TempBigEndian tempEndian (stream);

	// Read the number of tags.

	uint32 count = stream.Get_uint32 ();

	// There are only 5 possible tags, and no tag may be repeated. Therefore,
	// more than 5 tags is an error.

	if (count > 5)
		ThrowBadFormat ("too many tags in dng_image_stats");

	// Read each child tag.

	for (uint32 i = 0; i < count; i++)
		{
		
		// Read child tag code.

		uint32 childTagCode = stream.Get_uint32 ();

		// Read byte length of child tag data.

		uint32 length = stream.Get_uint32 ();

		// Byte length must be positive.

		if (length == 0)
			ThrowBadFormat ("child tag byte length must be > 0");
		
		// Byte length must be multiple of 4.

		if ((length & 3) != 0)
			ThrowBadFormat ("child tag byte length expected to be multiple of 4");

		// Check maximum value of byte length.

		constexpr uint32 kMaxBytes = 4 + kMaxSamples * 4 * (kMaxColorPlanes + 1);

		if (length > kMaxBytes)
			ThrowBadFormat ("child tag byte length too large");

		// Read all floats.

		std::vector<real32> *data = nullptr;

		switch (childTagCode)
			{
			
			case kTag_WeightedAverage:
				{
				data = &fWeightedAverage;
				break;
				}
				
			case kTag_Weights:
				{
				data = &fWeights;
				break;
				}
			
			case kTag_ColorAverage:
				{
				data = &fColorAverage;
				break;
				}
				
			default:
				break;
			
			}

		if (data)
			{

			const uint32 numFloats = (length >> 2);

			data->resize (numFloats);

			for (uint32 c = 0; c < numFloats; c++)
				(*data) [c] = stream.Get_real32 ();

			}

		else if (childTagCode == kTag_WeightedSamples)
			{

			const uint32 sampleCount = stream.Get_uint32 ();

			// Check sample count requirements.

			if (sampleCount == 0)
				ThrowBadFormat ("too few samples for weighted samples");

			if (sampleCount > kMaxSamples)
				ThrowBadFormat ("too many samples for weighted samples");

			// Check byte length.

			if (4 + (8 * sampleCount) != length)
				ThrowBadFormat ("mismatch byte length for weighted samples");

			// Read the child tag data.

			fWeightedSamples.resize (sampleCount);

			for (auto &sample : fWeightedSamples)
				{

				sample.fFrac  = stream.Get_real32 ();
				sample.fValue = stream.Get_real32 ();
				
				}
			
			}

		else if (childTagCode == kTag_ColorSamples)
			{
			
			const uint32 sampleCount = stream.Get_uint32 ();

			// Check sample count requirements.

			if (sampleCount == 0)
				ThrowBadFormat ("too few samples for color samples");

			if (sampleCount > kMaxSamples)
				ThrowBadFormat ("too many samples for color samples");

			// Infer plane count.

			const uint32 planes = ((length - 4) / sampleCount / 4) - 1;

			if (planes == 0)
				ThrowBadFormat ("unexpected 0 plane count for color samples");

			if (planes > kMaxColorPlanes)
				ThrowBadFormat ("too large plane count for color samples");

			if (4 + (sampleCount * 4 * (planes + 1)) != length)
				ThrowBadFormat ("mismatched plane count for color samples");

			// Read the child tag data.

			fColorSamples.resize (sampleCount);

			for (auto &sample : fColorSamples)
				{

				sample.fFrac = stream.Get_real32 ();

				sample.fValues.resize (planes);

				for (auto &value : sample.fValues)
					value = stream.Get_real32 ();

				}
			
			}

		else
			{
			
			ThrowBadFormat ("unsupported child tag code");
			
			}

		}
	
	}

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

#if qDNGValidate

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

static void DumpTag (const char *name,
					 const std::vector<real32> &values)
	{

	if (!values.empty ())
		{
	
		printf ("  %s: %.4f",
				name,
				values.front ());

		for (size_t i = 1; i < values.size (); i++)
			printf (", %.4f", values [i]);

		printf ("\n");

		}
	
	}

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

void dng_image_stats::Dump () const
	{

	printf ("ImageStats: %u child tag(s)\n", TagCount ());

	DumpTag ("weights", fWeights);
	
	DumpTag ("weighted average", fWeightedAverage);
	
	DumpTag ("color average", fColorAverage);

	if (!fWeightedSamples.empty ())
		{

		printf ("  weighted samples:\n");
		
		for (const auto &s : fWeightedSamples)
			{
			
			printf ("    frac: %.6f, value: %.6lf\n",
					s.fFrac,
					s.fValue);
			
			}
		
		}
	
	if (!fColorSamples.empty ())
		{

		printf ("  color samples:\n");
		
		for (const auto &s : fColorSamples)
			{
			
			printf ("    frac: %.6f, values: ",
					s.fFrac);

			if (s.fValues.empty ())
				continue;

			printf ("%.6f", s.fValues.front ());

			for (size_t i = 1; i < s.fValues.size (); i++)
				printf (", %.6f", s.fValues [i]);

			printf ("\n");
			
			}
		
		}
	
	}

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

#endif	// qDNGValidate

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