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

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_shared.cpp#2 $ */ 
/* $DateTime: 2012/06/14 20:24:41 $ */
/* $Change: 835078 $ */
/* $Author: tknoll $ */

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

#include "dng_shared.h"

#include "dng_camera_profile.h"
#include "dng_exceptions.h"
#include "dng_globals.h"
#include "dng_memory.h"
#include "dng_parse_utils.h"
#include "dng_safe_arithmetic.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"
							 		 
/*****************************************************************************/

dng_camera_profile_info::dng_camera_profile_info ()

	:	fBigEndian (false)
	
	,	fColorPlanes (0)
	
	,	fCalibrationIlluminant1 (lsUnknown)
	,	fCalibrationIlluminant2 (lsUnknown)

	,	fColorMatrix1 ()
	,	fColorMatrix2 ()
		
	,	fForwardMatrix1 ()
	,	fForwardMatrix2 ()
		
	,	fReductionMatrix1 ()
	,	fReductionMatrix2 ()
		
	,	fProfileCalibrationSignature ()
	
	,	fProfileName ()
	
	,	fProfileCopyright ()

	,	fEmbedPolicy (pepAllowCopying)
	
	,	fProfileHues (0)
	,	fProfileSats (0)
	,	fProfileVals (0)

	,	fHueSatDeltas1Offset (0)
	,	fHueSatDeltas1Count  (0)

	,	fHueSatDeltas2Offset (0)
	,	fHueSatDeltas2Count  (0)

	,	fHueSatMapEncoding (encoding_Linear)
	
	,	fLookTableHues (0)
	,	fLookTableSats (0)
	,	fLookTableVals (0)
		
	,	fLookTableOffset (0)
	,	fLookTableCount  (0)

	,	fLookTableEncoding (encoding_Linear)

	,	fBaselineExposureOffset (0, 100)
	
	,	fDefaultBlackRender (defaultBlackRender_Auto)

	,	fToneCurveOffset     (0)
	,	fToneCurveCount      (0)
	
	,	fUniqueCameraModel ()

	{
	
	}
							 		 
/*****************************************************************************/

dng_camera_profile_info::~dng_camera_profile_info ()
	{
	
	}
							 		 
/*****************************************************************************/

bool dng_camera_profile_info::ParseTag (dng_stream &stream,
										uint32 parentCode,
										uint32 tagCode,
										uint32 tagType,
										uint32 tagCount,
										uint64 tagOffset)
	{

	switch (tagCode)
		{
			
		case tcCalibrationIlluminant1:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCalibrationIlluminant1 = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CalibrationIlluminant1: %s\n",
						LookupLightSource (fCalibrationIlluminant1));
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcCalibrationIlluminant2:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCalibrationIlluminant2 = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CalibrationIlluminant2: %s\n",
						LookupLightSource (fCalibrationIlluminant2));
				
				}
				
			#endif
				
			break;
			
			}

		case tcColorMatrix1:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (fColorPlanes == 0)
				{
			
				fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);
									
				}
				
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fColorPlanes,
								 3,
								 fColorMatrix1))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ColorMatrix1:\n");
				
				DumpMatrix (fColorMatrix1);
								
				}
				
			#endif
				
			break;
			
			}

		case tcColorMatrix2:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			// Kludge - Hasselblad FFF files are very DNG-like, but sometimes
			// only have a ColorMatrix2 tag and no ColorMatrix1 tag.
			
			bool hasselbladHack = (fColorPlanes == 0);
			
			if (hasselbladHack)
				{
				
				fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);
				
				#if qDNGValidate
				
				ReportWarning ("ColorMatrix2 without ColorMatrix1");
				
				#endif
			
				}
			
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fColorPlanes,
								 3,
								 fColorMatrix2))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ColorMatrix2:\n");
				
				DumpMatrix (fColorMatrix2);
								
				}
				
			#endif
				
			if (hasselbladHack)
				{
				
				fColorMatrix1 = fColorMatrix2;
				
				fColorMatrix2 = dng_matrix ();
				
				}
				
			break;
			
			}

		case tcForwardMatrix1:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 3,
								 fColorPlanes,
								 fForwardMatrix1))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ForwardMatrix1:\n");
				
				DumpMatrix (fForwardMatrix1);
								
				}
				
			#endif
				
			break;
			
			}

		case tcForwardMatrix2:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 3,
								 fColorPlanes,
								 fForwardMatrix2))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ForwardMatrix2:\n");
				
				DumpMatrix (fForwardMatrix2);
								
				}
				
			#endif
				
			break;
			
			}

		case tcReductionMatrix1:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 3,
								 fColorPlanes,
								 fReductionMatrix1))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ReductionMatrix1:\n");
				
				DumpMatrix (fReductionMatrix1);
								
				}
				
			#endif
				
			break;
			
			}

		case tcReductionMatrix2:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 3,
								 fColorPlanes,
								 fReductionMatrix2))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ReductionMatrix2:\n");
				
				DumpMatrix (fReductionMatrix2);
								
				}
				
			#endif
				
			break;
			
			}

		case tcProfileCalibrationSignature:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fProfileCalibrationSignature,
							false);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("ProfileCalibrationSignature: ");
				
				DumpString (fProfileCalibrationSignature);
				
				printf ("\n");
				
				}

			#endif
			
			break;
			
			}
			
		case tcProfileName:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fProfileName,
							false);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ProfileName: ");
				
				DumpString (fProfileName);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcProfileCopyright:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fProfileCopyright,
							false);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("ProfileCopyright: ");
				
				DumpString (fProfileCopyright);
				
				printf ("\n");
				
				}

			#endif
			
			break;
			
			}

		case tcProfileEmbedPolicy:
			{
				
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fEmbedPolicy = stream.TagValue_uint32 (tagType);

			#if qDNGValidate

			if (gVerbose)
				{
				
				const char *policy;

				switch (fEmbedPolicy)
					{

					case pepAllowCopying:
						policy = "Allow copying";
						break;

					case pepEmbedIfUsed:
						policy = "Embed if used";
						break;

					case pepEmbedNever:
						policy = "Embed never";
						break;

					case pepNoRestrictions:
						policy = "No restrictions";
						break;

					default:
						policy = "INVALID VALUE";
	
					}

				printf ("ProfileEmbedPolicy: %s\n", policy);
								
				}

			#endif

			break;
			
			}

		case tcProfileHueSatMapDims:
			{
				
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 2, 3);
			
			fProfileHues = stream.TagValue_uint32 (tagType);
			fProfileSats = stream.TagValue_uint32 (tagType);

			if (tagCount > 2)
				fProfileVals = stream.TagValue_uint32 (tagType);
			else
				fProfileVals = 1;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ProfileHueSatMapDims: Hues = %u, Sats = %u, Vals = %u\n",
						(unsigned) fProfileHues,
						(unsigned) fProfileSats,
						(unsigned) fProfileVals);
								
				}
				
			#endif

			break;
			
			}

		case tcProfileHueSatMapData1:
			{

			if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
				return false;
				
			bool skipSat0 = (tagCount ==
							 SafeUint32Mult(fProfileHues,
											SafeUint32Sub(fProfileSats, 1u),
											fProfileVals,
											3u));
			
			if (!skipSat0)
				{
			
				if (!CheckTagCount (parentCode, tagCode, tagCount,
									SafeUint32Mult(fProfileHues, fProfileSats, fProfileVals, 3)))
					return false;
					
				}
				
			fBigEndian = stream.BigEndian ();
				
			fHueSatDeltas1Offset = tagOffset;
			fHueSatDeltas1Count  = tagCount;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ProfileHueSatMapData1:\n");
				
				DumpHueSatMap (stream,
							   fProfileHues,
							   fProfileSats,
							   fProfileVals,
							   skipSat0);
					
				}
				
			#endif

			break;
			
			}

		case tcProfileHueSatMapData2:
			{

			if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
				return false;
			
			bool skipSat0 = (tagCount ==
							 SafeUint32Mult(fProfileHues,
											SafeUint32Sub(fProfileSats, 1u),
											fProfileVals,
											3u));
			
			if (!skipSat0)
				{
			
				if (!CheckTagCount (parentCode, tagCode, tagCount,
									SafeUint32Mult(fProfileHues, fProfileSats, fProfileVals, 3)))
					return false;
					
				}
			
			fBigEndian = stream.BigEndian ();
				
			fHueSatDeltas2Offset = tagOffset;
			fHueSatDeltas2Count  = tagCount;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ProfileHueSatMapData2:\n");
				
				DumpHueSatMap (stream,
							   fProfileHues,
							   fProfileSats,
							   fProfileVals,
							   skipSat0);
					
				}
				
			#endif

			break;
			
			}

		case tcProfileHueSatMapEncoding:
			{

			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fHueSatMapEncoding = stream.TagValue_uint32 (tagType);

			#if qDNGValidate

			if (gVerbose)
				{
				
				const char *encoding = NULL;

				switch (fHueSatMapEncoding)
					{

					case encoding_Linear:
						encoding = "Linear";
						break;

					case encoding_sRGB:
						encoding = "sRGB";
						break;

					default:
						encoding = "INVALID VALUE";
	
					}

				printf ("ProfileHueSatMapEncoding: %s\n", encoding);
								
				}

			#endif

			break;
			
			}

		case tcProfileLookTableDims:
			{
				
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 2, 3);
			
			fLookTableHues = stream.TagValue_uint32 (tagType);
			fLookTableSats = stream.TagValue_uint32 (tagType);

			if (tagCount > 2)
				fLookTableVals = stream.TagValue_uint32 (tagType);
			else
				fLookTableVals = 1;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ProfileLookTableDims: Hues = %u, Sats = %u, Vals = %u\n",
						(unsigned) fLookTableHues,
						(unsigned) fLookTableSats,
						(unsigned) fLookTableVals);
								
				}
				
			#endif

			break;
			
			}

		case tcProfileLookTableData:
			{

			if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
				return false;
			
			bool skipSat0 = (tagCount ==
							 SafeUint32Mult(fLookTableHues,
											SafeUint32Sub(fLookTableSats, 1u),
											fLookTableVals,
											3u));
			
			if (!skipSat0)
				{
			
				if (!CheckTagCount (parentCode, tagCode, tagCount,
									SafeUint32Mult(fLookTableHues,
													fLookTableSats,
													fLookTableVals, 3)))
					return false;
					
				}
			
			fBigEndian = stream.BigEndian ();
				
			fLookTableOffset = tagOffset;
			fLookTableCount  = tagCount;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ProfileLookTableData:\n");
				
				DumpHueSatMap (stream,
							   fLookTableHues,
							   fLookTableSats,
							   fLookTableVals,
							   skipSat0);
					
				}
				
			#endif

			break;
			
			}

		case tcProfileLookTableEncoding:
			{

			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fLookTableEncoding = stream.TagValue_uint32 (tagType);

			#if qDNGValidate

			if (gVerbose)
				{
				
				const char *encoding = NULL;

				switch (fLookTableEncoding)
					{

					case encoding_Linear:
						encoding = "Linear";
						break;

					case encoding_sRGB:
						encoding = "sRGB";
						break;

					default:
						encoding = "INVALID VALUE";
	
					}

				printf ("ProfileLookTableEncoding: %s\n", encoding);
								
				}

			#endif

			break;
			
			}

		case tcBaselineExposureOffset:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBaselineExposureOffset = stream.TagValue_srational (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("BaselineExposureOffset: %+0.2f\n",
					    fBaselineExposureOffset.As_real64 ());
					    
				}
				
			#endif
				
			break;
			
			}
			
		case tcDefaultBlackRender:
			{

			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fDefaultBlackRender = stream.TagValue_uint32 (tagType);

			#if qDNGValidate

			if (gVerbose)
				{
				
				const char *setting = NULL;

				switch (fDefaultBlackRender)
					{

					case defaultBlackRender_Auto:
						setting = "Auto";
						break;

					case defaultBlackRender_None:
						setting = "None";
						break;

					default:
						setting = "INVALID VALUE";
	
					}

				printf ("DefaultBlackRender: %s\n", 
						setting);
								
				}

			#endif

			break;
			
			}

		case tcProfileToneCurve:
			{
				
			if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
				return false;
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 4, tagCount))
				return false;
		
			if ((tagCount & 1) != 0)
				{
				
				#if qDNGValidate
				
					{
						
					char message [256];
					
					sprintf (message,
							 "%s %s has odd count (%u)",
							 LookupParentCode (parentCode),
							 LookupTagCode (parentCode, tagCode),
							 (unsigned) tagCount);
							 
					ReportWarning (message);
								 
					}
					
				#endif
					
				return false;
				
				}
			
			fBigEndian = stream.BigEndian ();
				
			fToneCurveOffset = tagOffset;
			fToneCurveCount  = tagCount;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				DumpTagValues (stream,
							   "Coord",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);
					
								
				}
				
			#endif

			break;
			
			}

		case tcUniqueCameraModel:
			{
			
			// Note: This code is only used when parsing stand-alone
			// profiles.  The embedded profiles are assumed to be restricted
			// to the model they are embedded in.
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fUniqueCameraModel,
							false);
							
			bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();
			
			#if qDNGValidate
			
			if (didTrim)
				{
				
				ReportWarning ("UniqueCameraModel string has trailing blanks");
				
				}
			
			if (gVerbose)
				{
				
				printf ("UniqueCameraModel: ");
				
				DumpString (fUniqueCameraModel);
				
				printf ("\n");
				
				}
				
			#else
			
			(void) didTrim;		// Unused
				
			#endif
			
			break;
			
			}

		default:
			{
			
			return false;
			
			}
			
		}

	return true;
	
	}

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

bool dng_camera_profile_info::ParseExtended (dng_stream &stream)
	{

	try
		{
		
		// Offsets are relative to the start of this structure, not the entire file.

		uint64 startPosition = stream.Position ();

		// Read header. Like a TIFF header, but with different magic number
		// Plus all offsets are relative to the start of the IFD, not to the
		// stream or file.

		uint16 byteOrder = stream.Get_uint16 ();

		if (byteOrder == byteOrderMM)
			fBigEndian = true;
			
		else if (byteOrder == byteOrderII)
			fBigEndian = false;
			
		else
			return false;

		TempBigEndian setEndianness (stream, fBigEndian);

		uint16 magicNumber = stream.Get_uint16 ();

		if (magicNumber != magicExtendedProfile)
			{
			return false;
			}

		uint32 offset = stream.Get_uint32 ();

		stream.Skip (SafeUint32Sub(offset, 8u));

		// Start on IFD entries.

		uint32 ifdEntries = stream.Get_uint16 ();
	
		if (ifdEntries < 1)
			{
			return false;
			}
		
		for (uint32 tag_index = 0; tag_index < ifdEntries; tag_index++)
			{
			
			stream.SetReadPosition (startPosition + 8 + 2 + tag_index * 12);
			
			uint16 tagCode  = stream.Get_uint16 ();
			uint32 tagType  = stream.Get_uint16 ();
			uint32 tagCount = stream.Get_uint32 ();
			
			uint64 tagOffset = stream.Position ();

			if (SafeUint32Mult(TagTypeSize (tagType), tagCount) > 4)
				{

				tagOffset = startPosition + stream.Get_uint32 ();

				stream.SetReadPosition (tagOffset);

				}
				
			if (!ParseTag (stream,
						   0,
						   tagCode,
						   tagType,
						   tagCount,
						   tagOffset))
				{
				
				#if qDNGValidate
		
				if (gVerbose)
					{
							
					stream.SetReadPosition (tagOffset);
				
					printf ("*");
						
					DumpTagValues (stream,
								   LookupTagType (tagType),
								   0,
								   tagCode,
								   tagType,
								   tagCount);
					
					}
					
				#endif

				}

			}
			
		return true;

		}
		
	catch (...)
		{
		
		// Eat parsing errors.
		
		}

	return false;
	
	}

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

dng_shared::dng_shared ()
	
	:	fExifIFD 			 (0)
	,	fGPSInfo 			 (0)
	,	fInteroperabilityIFD (0)
	,	fKodakDCRPrivateIFD  (0)
	,	fKodakKDCPrivateIFD  (0)
		
	,	fXMPCount  (0)
	,	fXMPOffset (0)
		
	,	fIPTC_NAA_Count  (0)
	,	fIPTC_NAA_Offset (0)
	
	,	fMakerNoteCount  (0)
	,	fMakerNoteOffset (0)
	,	fMakerNoteSafety (0)
	
	,	fDNGVersion         (0)
	,	fDNGBackwardVersion (0)
	
	,	fUniqueCameraModel    ()
	,	fLocalizedCameraModel ()
	
	,	fCameraProfile ()
	
	,	fExtraCameraProfiles ()
	
	,	fCameraCalibration1 ()
	,	fCameraCalibration2 ()
		
	,	fCameraCalibrationSignature  ()

	,	fAnalogBalance ()
		
	,	fAsShotNeutral ()
		
	,	fAsShotWhiteXY ()
	
	,	fBaselineExposure      (0, 1)
	,	fBaselineNoise         (1, 1)
	,	fNoiseReductionApplied (0, 0)
	,	fBaselineSharpness     (1, 1)
	,	fLinearResponseLimit   (1, 1)
	,	fShadowScale           (1, 1)
	
	,	fHasBaselineExposure (false)
	,	fHasShadowScale      (false)
	
	,	fDNGPrivateDataCount  (0)
	,	fDNGPrivateDataOffset (0)
	
	,	fRawImageDigest    ()
	,	fNewRawImageDigest ()
	
	,	fRawDataUniqueID ()
	
	,	fOriginalRawFileName ()
		
	,	fOriginalRawFileDataCount  (0)
	,	fOriginalRawFileDataOffset (0)
	
	,	fOriginalRawFileDigest ()
		
	,	fAsShotICCProfileCount  (0)
	,	fAsShotICCProfileOffset (0)
	
	,	fAsShotPreProfileMatrix ()
		
	,	fCurrentICCProfileCount  (0)
	,	fCurrentICCProfileOffset (0)
	
	,	fCurrentPreProfileMatrix ()
	
	,	fColorimetricReference (crSceneReferred)

	,	fAsShotProfileName ()

	,	fNoiseProfile ()
	
	,	fOriginalDefaultFinalSize     ()
	,	fOriginalBestQualityFinalSize ()
		
	,	fOriginalDefaultCropSizeH ()
	,	fOriginalDefaultCropSizeV ()
		
	{
	
	}
	
/*****************************************************************************/

dng_shared::~dng_shared ()
	{
	
	}
		
/*****************************************************************************/

bool dng_shared::ParseTag (dng_stream &stream,
						   dng_exif &exif,
						   uint32 parentCode,
						   bool /* isMainIFD */,
						   uint32 tagCode,
						   uint32 tagType,
						   uint32 tagCount,
						   uint64 tagOffset,
						   int64 /* offsetDelta */)
	{
	
	if (parentCode == 0)
		{
		
		if (Parse_ifd0 (stream,
						exif,
						parentCode,
						tagCode,
						tagType,
						tagCount,
						tagOffset))
			{
			
			return true;
			
			}

		}
		
	if (parentCode == 0 ||
		parentCode == tcExifIFD)
		{
		
		if (Parse_ifd0_exif (stream,
							 exif,
						 	 parentCode,
						 	 tagCode,
						 	 tagType,
						 	 tagCount,
						 	 tagOffset))
			{
			
			return true;
			
			}

		}
		
	return false;
		
	}

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

// Parses tags that should only appear in IFD 0.

bool dng_shared::Parse_ifd0 (dng_stream &stream,
							 dng_exif & /* exif */,
							 uint32 parentCode,
							 uint32 tagCode,
							 uint32 tagType,
							 uint32 tagCount,
							 uint64 tagOffset)
	{
	
	switch (tagCode)
		{
			
		case tcXMP:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttByte, ttUndefined);
			
			fXMPCount  = tagCount;
			fXMPOffset = fXMPCount ? tagOffset : 0;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("XMP: Count = %u, Offset = %u\n",
						(unsigned) fXMPCount,
						(unsigned) fXMPOffset);
						
				if (fXMPCount)
					{
						
					DumpXMP (stream, fXMPCount);
					
					}
						
				}
				
			#endif
				
			break;
			
			}

		case tcIPTC_NAA:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttAscii, ttUndefined);
			
			fIPTC_NAA_Count = SafeUint32Mult(tagCount,
														  TagTypeSize(tagType));
			fIPTC_NAA_Offset = fIPTC_NAA_Count ? tagOffset : 0;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("IPTC/NAA: Count = %u, Offset = %u\n",
						(unsigned) fIPTC_NAA_Count,
						(unsigned) fIPTC_NAA_Offset);
						
				if (fIPTC_NAA_Count)
					{
						
					DumpHexAscii (stream, fIPTC_NAA_Count);
					
					}
					
				// Compute and output the digest.
				
				dng_memory_data buffer (fIPTC_NAA_Count);
				
				stream.SetReadPosition (fIPTC_NAA_Offset);
				
				stream.Get (buffer.Buffer (), fIPTC_NAA_Count);
				
				const uint8 *data = buffer.Buffer_uint8 ();
				
				uint32 count = fIPTC_NAA_Count;
				
				// Method 1: Counting all bytes (this is correct).
				
					{
					
					dng_md5_printer printer;
		
					printer.Process (data, count);
					
					printf ("IPTCDigest: ");
					
					DumpFingerprint (printer.Result ());
					
					printf ("\n");
					
					}
					
				// Method 2: Ignoring zero padding.
					
					{
					
					uint32 removed = 0;
			
					while ((removed < 3) && (count > 0) && (data [count - 1] == 0))
						{
						removed++;
						count--;
						}
						
					if (removed != 0)
						{
					
						dng_md5_printer printer;
			
						printer.Process (data, count);
						
						printf ("IPTCDigest (ignoring zero padding): ");
						
						DumpFingerprint (printer.Result ());
						
						printf ("\n");
						
						}
					
					}
					
				}
				
			#endif
				
			break;
			
			}

		case tcExifIFD:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fExifIFD = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("ExifIFD: %u\n", (unsigned) fExifIFD);
				}
				
			#endif
				
			break;
			
			}
		
		case tcGPSInfo:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fGPSInfo = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("GPSInfo: %u\n", (unsigned) fGPSInfo);
				}
				
			#endif
				
			break;
			
			}
				
		case tcKodakDCRPrivateIFD:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fKodakDCRPrivateIFD = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("KodakDCRPrivateIFD: %u\n", (unsigned) fKodakDCRPrivateIFD);
				}
				
			#endif
				
			break;
			
			}
		
		case tcKodakKDCPrivateIFD:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fKodakKDCPrivateIFD = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("KodakKDCPrivateIFD: %u\n", (unsigned) fKodakKDCPrivateIFD);
				}
				
			#endif
				
			break;
			
			}
		
		case tcDNGVersion:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttByte);
			
			CheckTagCount (parentCode, tagCode, tagCount, 4);
			
			uint32 b0 = stream.Get_uint8 ();
			uint32 b1 = stream.Get_uint8 ();
			uint32 b2 = stream.Get_uint8 ();
			uint32 b3 = stream.Get_uint8 ();
			
			fDNGVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("DNGVersion: %u.%u.%u.%u\n",
						(unsigned) b0,
						(unsigned) b1,
						(unsigned) b2,
						(unsigned) b3);
				}
				
			#endif
			
			break;
			
			}
				
		case tcDNGBackwardVersion:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttByte);
			
			CheckTagCount (parentCode, tagCode, tagCount, 4);
			
			uint32 b0 = stream.Get_uint8 ();
			uint32 b1 = stream.Get_uint8 ();
			uint32 b2 = stream.Get_uint8 ();
			uint32 b3 = stream.Get_uint8 ();
			
			fDNGBackwardVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("DNGBackwardVersion: %u.%u.%u.%u\n", 
						(unsigned) b0, 
						(unsigned) b1, 
						(unsigned) b2, 
						(unsigned) b3);
				}
				
			#endif
			
			break;
			
			}
				
		case tcUniqueCameraModel:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fUniqueCameraModel,
							false);
							
			bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();
			
			#if qDNGValidate
			
			if (didTrim)
				{
				
				ReportWarning ("UniqueCameraModel string has trailing blanks");
				
				}
			
			if (gVerbose)
				{
				
				printf ("UniqueCameraModel: ");
				
				DumpString (fUniqueCameraModel);
				
				printf ("\n");
				
				}
				
			#else
			
			(void) didTrim;		// Unused
				
			#endif
			
			break;
			
			}

		case tcLocalizedCameraModel:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fLocalizedCameraModel,
							false);
			
			bool didTrim = fLocalizedCameraModel.TrimTrailingBlanks ();
			
			#if qDNGValidate
			
			if (didTrim)
				{
				
				ReportWarning ("LocalizedCameraModel string has trailing blanks");
				
				}
			
			if (gVerbose)
				{
				
				printf ("LocalizedCameraModel: ");
				
				DumpString (fLocalizedCameraModel);
				
				printf ("\n");
				
				}
				
			#else
			
			(void) didTrim;		// Unused
				
			#endif
			
			break;
			
			}
			
		case tcCameraCalibration1:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fCameraProfile.fColorPlanes,
								 fCameraProfile.fColorPlanes,
								 fCameraCalibration1))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("CameraCalibration1:\n");
				
				DumpMatrix (fCameraCalibration1);
								
				}
				
			#endif
				
			break;
			
			}

		case tcCameraCalibration2:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fCameraProfile.fColorPlanes,
								 fCameraProfile.fColorPlanes,
								 fCameraCalibration2))
				return false;
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("CameraCalibration2:\n");
				
				DumpMatrix (fCameraCalibration2);
								
				}
				
			#endif
				
			break;
			
			}

		case tcCameraCalibrationSignature:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fCameraCalibrationSignature,
							false);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("CameraCalibrationSignature: ");
				
				DumpString (fCameraCalibrationSignature);
				
				printf ("\n");
				
				}

			#endif
			
			break;
			
			}
			
		case tcAnalogBalance:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			// Kludge - Hasselblad FFF files are very DNG-like, but sometimes
			// they don't have any ColorMatrix tags.
			
			bool hasselbladHack = (fDNGVersion == 0 &&
								   fCameraProfile.fColorPlanes == 0);
			
			if (hasselbladHack)
				{
				
				fCameraProfile.fColorPlanes = Pin_uint32 (0, tagCount, kMaxColorPlanes);
				
				#if qDNGValidate
				
				ReportWarning ("AnalogBalance without ColorMatrix1");
				
				#endif
			
				}
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			if (!ParseVectorTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fCameraProfile.fColorPlanes,
								 fAnalogBalance))
				return false;
	
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("AnalogBalance:");
				
				DumpVector (fAnalogBalance);
								
				}
				
			#endif
				
			break;
			
			}
			
		case tcAsShotNeutral:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			// Kludge - Hasselblad FFF files are very DNG-like, but sometimes
			// they don't have any ColorMatrix tags.
			
			bool hasselbladHack = (fDNGVersion == 0 &&
								   fCameraProfile.fColorPlanes == 0);
			
			if (hasselbladHack)
				{
				
				fCameraProfile.fColorPlanes = Pin_uint32 (0, tagCount, kMaxColorPlanes);
				
				#if qDNGValidate
				
				ReportWarning ("AsShotNeutral without ColorMatrix1");
				
				#endif
			
				}
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			if (!ParseVectorTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 fCameraProfile.fColorPlanes,
								 fAsShotNeutral))
				return false;
	
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("AsShotNeutral:");
				
				DumpVector (fAsShotNeutral);
								
				}
				
			#endif
				
			break;
			
			}
			
		case tcAsShotWhiteXY:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fAsShotWhiteXY.x = stream.TagValue_real64 (tagType);
			fAsShotWhiteXY.y = stream.TagValue_real64 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("AsShotWhiteXY: %0.4f %0.4f\n",
						fAsShotWhiteXY.x,
						fAsShotWhiteXY.y);
						
				}
				
			#endif
				
			break;
			
			}
			
		case tcBaselineExposure:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBaselineExposure = stream.TagValue_srational (tagType);
			
			fHasBaselineExposure = true;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("BaselineExposure: %+0.2f\n",
					    fBaselineExposure.As_real64 ());
					    
				}
				
			#endif
				
			break;
			
			}
			
		case tcBaselineNoise:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBaselineNoise = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("BaselineNoise: %0.2f\n",
						fBaselineNoise.As_real64 ());
						
				}
				
			#endif
				
			break;
			
			}
			
		case tcNoiseReductionApplied:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttRational))
				return false;
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
				return false;
			
			fNoiseReductionApplied = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("NoiseReductionApplied: %u/%u\n",
						(unsigned) fNoiseReductionApplied.n,
						(unsigned) fNoiseReductionApplied.d);
						
				}
				
			#endif
				
			break;
			
			}

		case tcNoiseProfile:
			{

			if (!CheckTagType (parentCode, tagCode, tagType, ttDouble))
				return false;

			// Must be an even, positive number of doubles in a noise profile.
			
			if (!tagCount || (tagCount & 1))
				return false;

			// Determine number of planes (i.e., half the number of doubles).

			const uint32 numPlanes = Pin_uint32 (0, 
												 tagCount >> 1, 
												 kMaxColorPlanes);

			// Parse the noise function parameters.

			dng_std_vector<dng_noise_function> noiseFunctions;

			for (uint32 i = 0; i < numPlanes; i++)
				{

				const real64 scale	= stream.TagValue_real64 (tagType);
				const real64 offset = stream.TagValue_real64 (tagType);

				noiseFunctions.push_back (dng_noise_function (scale, offset));

				}

			// Store the noise profile.

			fNoiseProfile = dng_noise_profile (noiseFunctions);

			// Debug.

			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("NoiseProfile:\n");
				
				printf ("  Planes: %u\n", (unsigned) numPlanes);
					
				for (uint32 plane = 0; plane < numPlanes; plane++)
					{

					printf ("  Noise function for plane %u: scale = %.8lf, offset = %.8lf\n",
							(unsigned) plane,
							noiseFunctions [plane].Scale  (),
							noiseFunctions [plane].Offset ());

					}
				
				}

			#endif
			
			break;
			
			}
			
		case tcBaselineSharpness:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBaselineSharpness = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("BaselineSharpness: %0.2f\n",
					    fBaselineSharpness.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcLinearResponseLimit:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fLinearResponseLimit = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("LinearResponseLimit: %0.2f\n",
						fLinearResponseLimit.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcShadowScale:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fShadowScale = stream.TagValue_urational (tagType);
			
			fHasShadowScale = true;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ShadowScale: %0.4f\n",
						fShadowScale.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcDNGPrivateData:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttByte);
			
			fDNGPrivateDataCount  = tagCount;
			fDNGPrivateDataOffset = tagOffset;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("DNGPrivateData: Count = %u, Offset = %u\n",
						(unsigned) fDNGPrivateDataCount,
						(unsigned) fDNGPrivateDataOffset);
						
				DumpHexAscii (stream, tagCount);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcMakerNoteSafety:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fMakerNoteSafety = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("MakerNoteSafety: %s\n",
						LookupMakerNoteSafety (fMakerNoteSafety));
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcRawImageDigest:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
				return false;
				
			stream.Get (fRawImageDigest.data, 16);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("RawImageDigest: ");
				
				DumpFingerprint (fRawImageDigest);
									
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcNewRawImageDigest:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
				return false;
				
			stream.Get (fNewRawImageDigest.data, 16);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("NewRawImageDigest: ");
				
				DumpFingerprint (fNewRawImageDigest);
									
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcRawDataUniqueID:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
				return false;
				
			stream.Get (fRawDataUniqueID.data, 16);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("RawDataUniqueID: ");
				
				DumpFingerprint (fRawDataUniqueID);
									
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcOriginalRawFileName:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fOriginalRawFileName,
							false);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("OriginalRawFileName: ");
				
				DumpString (fOriginalRawFileName);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcOriginalRawFileData:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fOriginalRawFileDataCount  = tagCount;
			fOriginalRawFileDataOffset = tagOffset;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("OriginalRawFileData: Count = %u, Offset = %u\n",
						(unsigned) fOriginalRawFileDataCount,
						(unsigned) fOriginalRawFileDataOffset);
						
				DumpHexAscii (stream, tagCount);
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcOriginalRawFileDigest:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
				return false;
				
			stream.Get (fOriginalRawFileDigest.data, 16);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("OriginalRawFileDigest: ");
				
				DumpFingerprint (fOriginalRawFileDigest);
									
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcAsShotICCProfile:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fAsShotICCProfileCount  = tagCount;
			fAsShotICCProfileOffset = tagOffset;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("AsShotICCProfile: Count = %u, Offset = %u\n",
						(unsigned) fAsShotICCProfileCount,
						(unsigned) fAsShotICCProfileOffset);
						
				DumpHexAscii (stream, tagCount);
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcAsShotPreProfileMatrix:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			uint32 rows = fCameraProfile.fColorPlanes;
			
			if (tagCount == fCameraProfile.fColorPlanes * 3)
				{
				rows = 3;
				}
			
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 rows,
								 fCameraProfile.fColorPlanes,
								 fAsShotPreProfileMatrix))
				return false;
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("AsShotPreProfileMatrix:\n");
				
				DumpMatrix (fAsShotPreProfileMatrix);
								
				}
				
			#endif
				
			break;
			
			}
			
		case tcCurrentICCProfile:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fCurrentICCProfileCount  = tagCount;
			fCurrentICCProfileOffset = tagOffset;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CurrentICCProfile: Count = %u, Offset = %u\n",
						(unsigned) fCurrentICCProfileCount,
						(unsigned) fCurrentICCProfileOffset);
						
				DumpHexAscii (stream, tagCount);
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcCurrentPreProfileMatrix:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
				return false;
				
			uint32 rows = fCameraProfile.fColorPlanes;
			
			if (tagCount == fCameraProfile.fColorPlanes * 3)
				{
				rows = 3;
				}
			
			if (!ParseMatrixTag (stream,
								 parentCode,
								 tagCode,
								 tagType,
								 tagCount,
								 rows,
								 fCameraProfile.fColorPlanes,
								 fCurrentPreProfileMatrix))
				return false;
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CurrentPreProfileMatrix:\n");
				
				DumpMatrix (fCurrentPreProfileMatrix);
								
				}
				
			#endif
				
			break;
			
			}
			
		case tcColorimetricReference:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fColorimetricReference = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ColorimetricReference: %s\n",
						LookupColorimetricReference (fColorimetricReference));
				
				}
				
			#endif
				
			break;
			
			}

		case tcExtraCameraProfiles:
			{
				
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1, tagCount);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ExtraCameraProfiles: %u\n", (unsigned) tagCount);
				
				}
				
			#endif
			
			fExtraCameraProfiles.reserve (tagCount);
			
			for (uint32 index = 0; index < tagCount; index++)
				{
				
				#if qDNGValidate

				if (gVerbose)
					{
					
					printf ("\nExtraCameraProfile [%u]:\n\n", (unsigned) index);
					
					}
					
				#endif
			
				stream.SetReadPosition (tagOffset + index * 4);
				
				uint32 profileOffset = stream.TagValue_uint32 (tagType);
				
				dng_camera_profile_info profileInfo;
				
				stream.SetReadPosition (profileOffset);
				
				if (profileInfo.ParseExtended (stream))
					{
					
					fExtraCameraProfiles.push_back (profileInfo);
					
					}
					
				else
					{
					
					#if qDNGValidate

					ReportWarning ("Unable to parse extra camera profile");
					
					#endif
			
					}
				
				}

			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("\nDone with ExtraCameraProfiles\n\n");
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcAsShotProfileName:
			{
						
			CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fAsShotProfileName,
							false);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("AsShotProfileName: ");
				
				DumpString (fAsShotProfileName);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcOriginalDefaultFinalSize:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fOriginalDefaultFinalSize.h = stream.TagValue_int32 (tagType);
			fOriginalDefaultFinalSize.v = stream.TagValue_int32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("OriginalDefaultFinalSize: H = %d V = %d\n",
						(int) fOriginalDefaultFinalSize.h,
						(int) fOriginalDefaultFinalSize.v);
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcOriginalBestQualityFinalSize:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fOriginalBestQualityFinalSize.h = stream.TagValue_int32 (tagType);
			fOriginalBestQualityFinalSize.v = stream.TagValue_int32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("OriginalBestQualityFinalSize: H = %d V = %d\n",
						(int) fOriginalBestQualityFinalSize.h,
						(int) fOriginalBestQualityFinalSize.v);
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcOriginalDefaultCropSize:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fOriginalDefaultCropSizeH = stream.TagValue_urational (tagType);
			fOriginalDefaultCropSizeV = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("OriginalDefaultCropSize: H = %0.2f V = %0.2f\n",
						fOriginalDefaultCropSizeH.As_real64 (),
						fOriginalDefaultCropSizeV.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
			
		default:
			{
			
			// The main camera profile tags also appear in IFD 0
			
			return fCameraProfile.ParseTag (stream,
											parentCode,
											tagCode,
											tagType,
											tagCount,
											tagOffset);
			
			}
			
		}

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

// Parses tags that should only appear in IFD 0 or EXIF IFD.

bool dng_shared::Parse_ifd0_exif (dng_stream &stream,
								  dng_exif & /* exif */,
						  	   	  uint32 parentCode,
						  	      uint32 tagCode,
						  	      uint32 tagType,
						  	      uint32 tagCount,
						  	      uint64 tagOffset)
	{
	
	switch (tagCode)
		{
		
		case tcMakerNote:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fMakerNoteCount  = tagCount;
			fMakerNoteOffset = tagOffset;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("MakerNote: Count = %u, Offset = %u\n",
						(unsigned) fMakerNoteCount,
						(unsigned) fMakerNoteOffset);
						
				DumpHexAscii (stream, tagCount);
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcInteroperabilityIFD:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fInteroperabilityIFD = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				printf ("InteroperabilityIFD: %u\n", (unsigned) fInteroperabilityIFD);
				}
				
			#endif
			
			break;
			
			}
			
		default:
			{
			
			return false;
			
			}
			
		}
		
	return true;
	
	}
							   
/*****************************************************************************/

void dng_shared::PostParse (dng_host & /* host */,
							dng_exif & /* exif */)
	{
	
	// Fill in default values for DNG images.
	
	if (fDNGVersion != 0)
		{
		
		// Support for DNG versions before 1.0.0.0.
		
		if (fDNGVersion < dngVersion_1_0_0_0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("DNGVersion less than 1.0.0.0");
						 
			#endif
			
			// The CalibrationIlluminant tags were added just before
			// DNG version 1.0.0.0, and were hardcoded before that.
			
			fCameraProfile.fCalibrationIlluminant1 = lsStandardLightA;
			fCameraProfile.fCalibrationIlluminant2 = lsD65;
			
			fDNGVersion = dngVersion_1_0_0_0;

			}
		
		// Default value for DNGBackwardVersion tag.
		
		if (fDNGBackwardVersion == 0)
			{
			
			fDNGBackwardVersion = fDNGVersion & 0xFFFF0000;
			
			}
	
		// Check DNGBackwardVersion value.
		
		if (fDNGBackwardVersion < dngVersion_1_0_0_0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("DNGBackwardVersion less than 1.0.0.0");
						 
			#endif
			
			fDNGBackwardVersion = dngVersion_1_0_0_0;
				
			}

		if (fDNGBackwardVersion > fDNGVersion)
			{
			
			#if qDNGValidate
			
			ReportWarning ("DNGBackwardVersion > DNGVersion");
						 
			#endif
			
			fDNGBackwardVersion = fDNGVersion;
					
			}

		// Check UniqueCameraModel.
		
		if (fUniqueCameraModel.IsEmpty ())
			{
			
			#if qDNGValidate
			
			ReportWarning ("Missing or invalid UniqueCameraModel");
						 
			#endif
			
			fUniqueCameraModel.Set ("Digital Negative");
			
			}
		
		// If we don't know the color depth yet, it must be a monochrome DNG.
		
		if (fCameraProfile.fColorPlanes == 0)
			{
			
			fCameraProfile.fColorPlanes = 1;
			
			}
			
		// Check color info.
		
		if (fCameraProfile.fColorPlanes > 1)
			{
			
			// Check illuminant pair.
			
			if (fCameraProfile.fColorMatrix2.NotEmpty ())
				{
				
				if (fCameraProfile.fCalibrationIlluminant1 == lsUnknown ||
					(fCameraProfile.fCalibrationIlluminant2 == lsUnknown ||
					(fCameraProfile.fCalibrationIlluminant1 == fCameraProfile.fCalibrationIlluminant2)))
					{
						
					#if qDNGValidate
			
					ReportWarning ("Invalid CalibrationIlluminant pair");
								 
					#endif
								 
					fCameraProfile.fColorMatrix2 = dng_matrix ();
				
					}

				}
				
			// If the colorimetric reference is the ICC profile PCS, then the
			// data must already be white balanced.  The "AsShotWhiteXY" is required
			// to be the ICC Profile PCS white point.
			
			if (fColorimetricReference == crICCProfilePCS)
				{
				
				if (fAsShotNeutral.NotEmpty ())
					{
					
					#if qDNGValidate
				
					ReportWarning ("AsShotNeutral not allowed for this "
								   "ColorimetricReference value");
								 
					#endif
					
					fAsShotNeutral.Clear ();
					
					}
					
				dng_xy_coord pcs = PCStoXY ();
				
				#if qDNGValidate
					
				if (fAsShotWhiteXY.IsValid ())
					{
					
					if (Abs_real64 (fAsShotWhiteXY.x - pcs.x) > 0.01 ||
						Abs_real64 (fAsShotWhiteXY.y - pcs.y) > 0.01)
						{
						
						ReportWarning ("AsShotWhiteXY does not match the ICC Profile PCS");

						}
						
					}
									 
				#endif
				
				fAsShotWhiteXY = pcs;

				}
				
			else
				{
				
				// Warn if both AsShotNeutral and AsShotWhiteXY are specified.
					
				if (fAsShotNeutral.NotEmpty () && fAsShotWhiteXY.IsValid ())
					{
						
					#if qDNGValidate
				
					ReportWarning ("Both AsShotNeutral and AsShotWhiteXY included");
								 
					#endif
					
					fAsShotWhiteXY = dng_xy_coord ();
									 
					}
					
				// Warn if neither AsShotNeutral nor AsShotWhiteXY are specified.
					
				#if qDNGValidate
				
				if (fAsShotNeutral.IsEmpty () && !fAsShotWhiteXY.IsValid ())
					{
					
					ReportWarning ("Neither AsShotNeutral nor AsShotWhiteXY included",
								   "legal but not recommended");
								 
					}
							
				#endif
					
				}
				
			// Default values of calibration signatures are required for legacy
			// compatiblity.

			if (fCameraProfile.fCalibrationIlluminant1 == lsStandardLightA &&
				fCameraProfile.fCalibrationIlluminant2 == lsD65            &&
				fCameraCalibration1.Rows () == fCameraProfile.fColorPlanes &&
				fCameraCalibration1.Cols () == fCameraProfile.fColorPlanes &&
				fCameraCalibration2.Rows () == fCameraProfile.fColorPlanes &&
				fCameraCalibration2.Cols () == fCameraProfile.fColorPlanes &&
				fCameraCalibrationSignature.IsEmpty ()                     &&
				fCameraProfile.fProfileCalibrationSignature.IsEmpty ()     )
				{

				fCameraCalibrationSignature.Set (kAdobeCalibrationSignature);
					
				fCameraProfile.fProfileCalibrationSignature.Set (kAdobeCalibrationSignature);

				}

			}
			
		// Check BaselineNoise.

		if (fBaselineNoise.As_real64 () <= 0.0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("Invalid BaselineNoise");
						 
			#endif
			
			fBaselineNoise = dng_urational (1, 1);
							 
			}
			
		// Check BaselineSharpness.
		
		if (fBaselineSharpness.As_real64 () <= 0.0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("Invalid BaselineSharpness");
						 
			#endif
			
			fBaselineSharpness = dng_urational (1, 1);
							 
			}

		// Check NoiseProfile.

		if (!fNoiseProfile.IsValid () && fNoiseProfile.NumFunctions () != 0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("Invalid NoiseProfile");
						 
			#endif
			
			fNoiseProfile = dng_noise_profile ();
							 
			}
			
		// Check LinearResponseLimit.
		
		if (fLinearResponseLimit.As_real64 () < 0.5 ||
			fLinearResponseLimit.As_real64 () > 1.0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("Invalid LinearResponseLimit");
						 
			#endif
			
			fLinearResponseLimit = dng_urational (1, 1);
							 
			}
			
		// Check ShadowScale.
		
		if (fShadowScale.As_real64 () <= 0.0)
			{
			
			#if qDNGValidate
			
			ReportWarning ("Invalid ShadowScale");
						 
			#endif
							 
			fShadowScale = dng_urational (1, 1);
							 
			}
		
		}
	
	}
							   
/*****************************************************************************/

bool dng_shared::IsValidDNG ()
	{
	
	// Check DNGVersion value.
	
	if (fDNGVersion < dngVersion_1_0_0_0)
		{
		
		#if qDNGValidate
		
		if (fDNGVersion != dngVersion_None)
			{

			ReportError ("Invalid DNGVersion");

			}
					 
		#if qDNGValidateTarget
		
		else
			{

			ReportError ("Missing DNGVersion");

			}
			
		#endif
		
		#endif
					 
		return false;
			
		}
		
	// Check DNGBackwardVersion value.
	
	if (fDNGBackwardVersion > dngVersion_Current)
		{
		
		#if qDNGValidate
		
		ReportError ("DNGBackwardVersion (or DNGVersion) is too high");
		
		#endif
		
		ThrowUnsupportedDNG ();
			
		}
		
	// Check color transform info.
	
	if (fCameraProfile.fColorPlanes > 1)
		{
		
		// CameraCalibration1 is optional, but it must be valid if present.
		
		if (fCameraCalibration1.Cols () != 0 ||
			fCameraCalibration1.Rows () != 0)
			{
			
			if (fCameraCalibration1.Cols () != fCameraProfile.fColorPlanes ||
				fCameraCalibration1.Rows () != fCameraProfile.fColorPlanes)
				{
				
				#if qDNGValidate
		
				ReportError ("CameraCalibration1 is wrong size");
							 
				#endif

				return false;
				
				}
				
			// Make sure it is invertable.
			
			try
				{
				
				(void) Invert (fCameraCalibration1);
				
				}
				
			catch (...)
				{
				
				#if qDNGValidate
		
				ReportError ("CameraCalibration1 is not invertable");
							 
				#endif
							 
				return false;
			
				}
				
			}
		
		// CameraCalibration2 is optional, but it must be valid if present.
		
		if (fCameraCalibration2.Cols () != 0 ||
			fCameraCalibration2.Rows () != 0)
			{
			
			if (fCameraCalibration2.Cols () != fCameraProfile.fColorPlanes ||
				fCameraCalibration2.Rows () != fCameraProfile.fColorPlanes)
				{
				
				#if qDNGValidate
		
				ReportError ("CameraCalibration2 is wrong size");
							 
				#endif

				return false;
				
				}
			
			// Make sure it is invertable.
			
			try
				{
				
				(void) Invert (fCameraCalibration2);
				
				}
				
			catch (...)
				{
				
				#if qDNGValidate
	
				ReportError ("CameraCalibration2 is not invertable");
							 
				#endif
							 
				return false;
			
				}
					
			}
			
		// Check analog balance
		
		dng_matrix analogBalance;
		
		if (fAnalogBalance.NotEmpty ())
			{
			
			analogBalance = fAnalogBalance.AsDiagonal ();
			
			try
				{
				
				(void) Invert (analogBalance);
				
				}
				
			catch (...)
				{
				
				#if qDNGValidate
		
				ReportError ("AnalogBalance is not invertable");
							 
				#endif
							 
				return false;
			
				}
				
			}
				
		}
			
	return true;
	
	}
	
/*****************************************************************************/