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

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_ifd.cpp#3 $ */ 
/* $DateTime: 2012/06/05 11:05:39 $ */
/* $Change: 833352 $ */
/* $Author: tknoll $ */

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

#include "dng_ifd.h"

#include "dng_exceptions.h"
#include "dng_flags.h"
#include "dng_globals.h"
#include "dng_ifd.h"
#include "dng_types.h"
#include "dng_parse_utils.h"
#include "dng_read_image.h"
#include "dng_stream.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"

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

dng_preview_info::dng_preview_info ()
	
	:	fIsPrimary          (true)
	,	fApplicationName    ()
	,	fApplicationVersion ()
	,	fSettingsName       ()
	,	fSettingsDigest     ()
	,	fColorSpace			(previewColorSpace_MaxEnum)
	,	fDateTime			()
	,	fRawToPreviewGain   (1.0)
	,	fCacheVersion		(0)
	
	{
	
	}

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

dng_preview_info::~dng_preview_info ()
	{
	
	}

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

dng_ifd::dng_ifd ()

	:	fUsesNewSubFileType (false)
	,	fNewSubFileType     (0)

	,	fImageWidth  (0)
	,	fImageLength (0)

	,	fCompression (ccUncompressed)
	,	fPredictor   (cpNullPredictor)

	,	fPhotometricInterpretation (0xFFFFFFFF)

	,	fFillOrder (1)

	,	fOrientation          (0)
	,	fOrientationType      (0)
	,	fOrientationOffset    (kDNGStreamInvalidOffset)
	,	fOrientationBigEndian (false)

	,	fSamplesPerPixel (1)

	,	fPlanarConfiguration (pcInterleaved)

	,	fXResolution    (0.0)
	,	fYResolution    (0.0)
	,	fResolutionUnit (0)
		
	,	fUsesStrips (false)
	,	fUsesTiles  (false)
	
	,	fTileWidth  (0)
	,	fTileLength (0)
	
	,	fTileOffsetsType   (0)
	,	fTileOffsetsCount  (0)
	,	fTileOffsetsOffset (0)
	
	,	fTileByteCountsType   (0)
	,	fTileByteCountsCount  (0)
	,	fTileByteCountsOffset (0)
	
	,	fSubIFDsCount  (0)
	,	fSubIFDsOffset (0)
	
	,	fExtraSamplesCount (0)
	
	,	fJPEGTablesCount  (0)
	,	fJPEGTablesOffset (0)
		
	,	fJPEGInterchangeFormat		 (0)
	,	fJPEGInterchangeFormatLength (0)

	,	fYCbCrCoefficientR (0.0)
	,	fYCbCrCoefficientG (0.0)
	,	fYCbCrCoefficientB (0.0)
	
	,	fYCbCrSubSampleH (0)
	,	fYCbCrSubSampleV (0)
	
	,	fYCbCrPositioning (0)
	
	,	fCFARepeatPatternRows (0)
	,	fCFARepeatPatternCols (0)
	
	,	fCFALayout (1)
	
	,	fLinearizationTableType   (0)
	,	fLinearizationTableCount  (0)
	,	fLinearizationTableOffset (0)

	,	fBlackLevelRepeatRows (1)
	,	fBlackLevelRepeatCols (1)
	
	,	fBlackLevelDeltaHType   (0)
	,	fBlackLevelDeltaHCount  (0)
	,	fBlackLevelDeltaHOffset (0)

	,	fBlackLevelDeltaVType   (0)
	,	fBlackLevelDeltaVCount  (0)
	,	fBlackLevelDeltaVOffset (0)
	
	,	fDefaultScaleH (1, 1)
	,	fDefaultScaleV (1, 1)
	
	,	fBestQualityScale (1, 1)
	
	,	fDefaultCropOriginH (0, 1)
	,	fDefaultCropOriginV (0, 1)
	
	,	fDefaultCropSizeH ()
	,	fDefaultCropSizeV ()
	
	,	fDefaultUserCropT (0, 1)
	,	fDefaultUserCropL (0, 1)
	,	fDefaultUserCropB (1, 1)
	,	fDefaultUserCropR (1, 1)
	
	,	fBayerGreenSplit (0)
	
	,	fChromaBlurRadius ()
	
	,	fAntiAliasStrength (1, 1)
	
	,	fActiveArea ()
	
	,	fMaskedAreaCount (0)
	
	,	fRowInterleaveFactor (1)
	
	,	fSubTileBlockRows (1)
	,	fSubTileBlockCols (1)
	
	,	fPreviewInfo ()
	
	,	fOpcodeList1Count  (0)
	,	fOpcodeList1Offset (0)
	
	,	fOpcodeList2Count  (0)
	,	fOpcodeList2Offset (0)
	
	,	fOpcodeList3Count  (0)
	,	fOpcodeList3Offset (0)
	
	,	fLosslessJPEGBug16 (false)
	
	,	fSampleBitShift (0)
		
	,	fThisIFD (0)
	,	fNextIFD (0)
	
	,	fCompressionQuality (-1)
	
	,	fPatchFirstJPEGByte (false)

	{
	
	uint32 j;
	uint32 k;
	uint32 n;
	
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		fBitsPerSample [j] = 0;
		}
	
	for (j = 0; j < kMaxTileInfo; j++)
		{
		fTileOffset    [j] = 0;
		fTileByteCount [j] = 0;
		}
	
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		fExtraSamples [j] = esUnspecified;
		}
	
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		fSampleFormat [j] = sfUnsignedInteger;
		}
		
	for (j = 0; j < 6; j++)
		{
		fReferenceBlackWhite [j] = 0.0;
		}
	
	for (j = 0; j < kMaxCFAPattern; j++)
		for (k = 0; k < kMaxCFAPattern; k++)
			{
			fCFAPattern [j] [k] = 255;
			}
			
	for (j = 0; j < kMaxColorPlanes; j++)
		{
		fCFAPlaneColor [j] = (uint8) (j < 3 ? j : 255);
		}
		
	for (j = 0; j < kMaxBlackPattern; j++)
		for (k = 0; k < kMaxBlackPattern; k++)
			for (n = 0; n < kMaxSamplesPerPixel; n++)
				{
				fBlackLevel [j] [k] [n] = 0.0;
				}
			
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		fWhiteLevel [j] = -1.0;		// Don't know real default yet.
		}
	
	}
	
/*****************************************************************************/

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

// Parses tags that should only appear in IFDs that contain images.

bool dng_ifd::ParseTag (dng_stream &stream,
						uint32 parentCode,
						uint32 tagCode,
						uint32 tagType,
						uint32 tagCount,
						uint64 tagOffset)
	{
	
	uint32 j;
	uint32 k;
	uint32 n;
	
	switch (tagCode)
		{
		
		case tcNewSubFileType:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fUsesNewSubFileType = true;
			
			fNewSubFileType = stream.TagValue_uint32 (tagType);
			
			fPreviewInfo.fIsPrimary = (fNewSubFileType == sfPreviewImage);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("NewSubFileType: %s\n",
						LookupNewSubFileType (fNewSubFileType));

				}
				
			#endif
				
			break;
			
			}

		case tcImageWidth:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fImageWidth = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("ImageWidth: %u\n", (unsigned) fImageWidth);
				}
				
			#endif
				
			break;
			
			}
		
		case tcImageLength:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fImageLength = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("ImageLength: %u\n", (unsigned) fImageLength);
				}
				
			#endif
				
			break;
			
			}
				
		case tcBitsPerSample:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1, 0x0FFFF);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("BitsPerSample:");
				}
				
			#endif
			
			bool extrasMatch = true;
			
			for (j = 0; j < tagCount; j++)
				{
				
				uint32 x = stream.TagValue_uint32 (tagType);
				
				const uint32 maxBitsPerSample = 32;
				
				if (j < kMaxSamplesPerPixel)
					{
					
					if (x > maxBitsPerSample)
						{
						ThrowBadFormat ("BitsPerSample out of bounds.");
						}
						
					fBitsPerSample [j] = x;
					}
					
				else if (x != fBitsPerSample [kMaxSamplesPerPixel - 1])
					{
					extrasMatch = false;
					}
					
				#if qDNGValidate
			
				if (gVerbose)
					{
					printf (" %u", (unsigned) x);
					}
					
				#endif
				
				}
					
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("\n");
				}
				
			#endif
			
			if (!extrasMatch)
				{
				
				#if qDNGValidate
			
				ReportError ("BitsPerSample not constant");
				
				#endif

				ThrowBadFormat ();
				
				}
			
			break;
			
			}
				
		case tcCompression:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCompression = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("Compression: %s\n",
					    LookupCompression (fCompression));
					    
				}
				
			#endif
	
			// Correct a common TIFF writer mistake.
			
			if (fCompression == 0)
				{
				
				#if qDNGValidate
				
					{
						
					char message [256];
					
					sprintf (message,
							 "%s has invalid zero compression code",
							 LookupParentCode (parentCode));
							 
					ReportWarning (message);
								 
					}
				
				#endif
				
				fCompression = ccUncompressed;
				
				}
				
			break;
			
			}
				
		case tcPhotometricInterpretation:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPhotometricInterpretation = stream.TagValue_uint32 (tagType);
							
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("PhotometricInterpretation: %s\n",
						LookupPhotometricInterpretation (fPhotometricInterpretation));
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcFillOrder:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFillOrder = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("FillOrder: %u\n", (unsigned) fFillOrder);
				}
				
			#endif
			
			break;
			
			}

		case tcStripOffsets:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			fUsesStrips = true;
			
			fTileOffsetsType   = tagType;
			fTileOffsetsCount  = tagCount;
			fTileOffsetsOffset = tagOffset;
			
			if (tagCount <= kMaxTileInfo)
				{
				
				for (j = 0; j < tagCount; j++)
					{
				
					fTileOffset [j] = stream.TagValue_uint32 (tagType);
					
					}
					
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				stream.SetReadPosition (tagOffset);
				
				DumpTagValues (stream,
							   "Offset",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);

				}
				
			#endif
			
			break;
			
			}
				
		case tcOrientation:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fOrientationType      = tagType;
			fOrientationOffset    = stream.PositionInOriginalFile ();
			fOrientationBigEndian = stream.BigEndian ();
			
			fOrientation = stream.TagValue_uint32 (tagType);
				
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("Orientation: %s\n",
					    LookupOrientation (fOrientation));
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcSamplesPerPixel:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSamplesPerPixel = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("SamplesPerPixel: %u\n", (unsigned) fSamplesPerPixel);
				}
				
			#endif

			break;
			
			}

		case tcRowsPerStrip:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fUsesStrips = true;
			
			fTileLength = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("RowsPerStrip: %u\n", (unsigned) fTileLength);
				}
				
			#endif

			break;
			
			}
				
		case tcStripByteCounts:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			fUsesStrips = true;
			
			fTileByteCountsType   = tagType;
			fTileByteCountsCount  = tagCount;
			fTileByteCountsOffset = tagOffset;
			
			if (tagCount <= kMaxTileInfo)
				{
				
				for (j = 0; j < tagCount; j++)
					{
				
					fTileByteCount [j] = stream.TagValue_uint32 (tagType);
					
					}
					
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				stream.SetReadPosition (tagOffset);
				
				DumpTagValues (stream,
							   "Count",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);
							   
				}
				
			#endif

			break;
			
			}
				
		case tcXResolution:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fXResolution = stream.TagValue_real64 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("XResolution: %0.2f\n", fXResolution);
				}
				
			#endif
			
			break;
			
			}
			
		case tcYResolution:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fYResolution = stream.TagValue_real64 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("YResolution: %0.2f\n", fYResolution);
				}
				
			#endif
				
			break;
			
			}
				
		case tcPlanarConfiguration:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPlanarConfiguration = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("PlanarConfiguration: %u\n", (unsigned) fPlanarConfiguration);
				}
			
			#endif
				
			break;
			
			}

		case tcResolutionUnit:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fResolutionUnit = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("ResolutionUnit: %s\n",
					    LookupResolutionUnit (fResolutionUnit));
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcPredictor:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPredictor = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("Predictor: %s\n",
						LookupPredictor (fPredictor));
					    
				}
				
			#endif
				
			break;
			
			}
				
		case tcTileWidth:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fUsesTiles = true;
			
			fTileWidth = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("TileWidth: %u\n", (unsigned) fTileWidth);
				}
			
			#endif
				
			break;
			
			}
			
		case tcTileLength:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fUsesTiles = true;
			
			fTileLength = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("TileLength: %u\n", (unsigned) fTileLength);
				}
				
			#endif
			
			break;
			
			}
			
		case tcTileOffsets:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			fUsesTiles = true;
			
			fTileOffsetsType   = tagType;
			fTileOffsetsCount  = tagCount;
			fTileOffsetsOffset = tagOffset;
			
			if (tagCount <= kMaxTileInfo)
				{
				
				for (j = 0; j < tagCount; j++)
					{
				
					fTileOffset [j] = stream.TagValue_uint32 (tagType);
					
					}
					
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				stream.SetReadPosition (tagOffset);
				
				DumpTagValues (stream,
							   "Offset",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);
							   
				}
				
			#endif
			
			break;
			
			}
			
		case tcTileByteCounts:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			fUsesTiles = true;
			
			fTileByteCountsType   = tagType;
			fTileByteCountsCount  = tagCount;
			fTileByteCountsOffset = tagOffset;
			
			if (tagCount <= kMaxTileInfo)
				{
				
				for (j = 0; j < tagCount; j++)
					{
				
					fTileByteCount [j] = stream.TagValue_uint32 (tagType);
					
					}
					
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				stream.SetReadPosition (tagOffset);
				
				DumpTagValues (stream,
							   "Count",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);
							   
				}
				
			#endif
			
			break;
			
			}
				
		case tcSubIFDs:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
			
			fSubIFDsCount  = tagCount;
			fSubIFDsOffset = tagOffset;
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				DumpTagValues (stream,
							   "IFD",
							   parentCode,
							   tagCode,
							   ttLong,
							   tagCount);
				
				}
				
			#endif
			
			break;
			
			}
				
		case tcExtraSamples:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1, fSamplesPerPixel);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("ExtraSamples:");
				}
				
			#endif
			
			fExtraSamplesCount = tagCount;
			
			for (j = 0; j < tagCount; j++)
				{
				
				uint32 x = stream.TagValue_uint32 (tagType);
				
				if (j < kMaxSamplesPerPixel)
					{
					fExtraSamples [j] = x;
					}
					
				#if qDNGValidate
			
				if (gVerbose)
					{
					printf (" %u", (unsigned) x);
					}
					
				#endif
				
				}
					
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("\n");
				}
				
			#endif
			
			break;
			
			}
				
		case tcSampleFormat:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, fSamplesPerPixel);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("SampleFormat:");
				}
				
			#endif
			
			bool extrasMatch = true;
			
			for (j = 0; j < tagCount; j++)
				{
				
				uint32 x = stream.TagValue_uint32 (tagType);
				
				if (j < kMaxSamplesPerPixel)
					{
					fSampleFormat [j] = x;
					}
					
				else if (x != fSampleFormat [kMaxSamplesPerPixel - 1])
					{
					extrasMatch = false;
					}
					
				#if qDNGValidate
			
				if (gVerbose)
					{
					printf (" %s", LookupSampleFormat (x));
					}
					
				#endif
				
				}
					
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("\n");
				}
				
			#endif
			
			if (!extrasMatch)
				{
				
				#if qDNGValidate
				
				ReportError ("SampleFormat not constant");
			
				#endif
				
				ThrowBadFormat ();
				
				}
			
			break;
			
			}
				
		case tcJPEGTables:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fJPEGTablesCount  = tagCount;
			fJPEGTablesOffset = tagOffset;
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("JPEGTables: count = %u, offset = %u\n",
						(unsigned) fJPEGTablesCount,
						(unsigned) fJPEGTablesOffset);
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcJPEGInterchangeFormat:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fJPEGInterchangeFormat = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("JPEGInterchangeFormat: %u\n",
						(unsigned) fJPEGInterchangeFormat);
				}
				
			#endif
			
			break;
			
			}
			
		case tcJPEGInterchangeFormatLength:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fJPEGInterchangeFormatLength = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("JPEGInterchangeFormatLength: %u\n",
						(unsigned) fJPEGInterchangeFormatLength);
				}
				
			#endif
			
			break;
			
			}
			
		case tcYCbCrCoefficients:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 3))
				{
				return false;
				}
			
			fYCbCrCoefficientR = stream.TagValue_real64 (tagType);
			fYCbCrCoefficientG = stream.TagValue_real64 (tagType);
			fYCbCrCoefficientB = stream.TagValue_real64 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("YCbCrCoefficients: R = %0.3f, G = %0.3f, B = %0.3f\n",
						fYCbCrCoefficientR,
						fYCbCrCoefficientG,
						fYCbCrCoefficientB);
						
				}
				
			#endif
			
			break;
			
			}

		case tcYCbCrSubSampling:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				{
				return false;
				}
			
			fYCbCrSubSampleH = stream.TagValue_uint32 (tagType);
			fYCbCrSubSampleV = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("YCbCrSubSampling: H = %u, V = %u\n",
						(unsigned) fYCbCrSubSampleH,
						(unsigned) fYCbCrSubSampleV);
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcYCbCrPositioning:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fYCbCrPositioning = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("YCbCrPositioning: %u\n",
						(unsigned) fYCbCrPositioning);
						
				}
				
			#endif
			
			break;
			
			}

		case tcReferenceBlackWhite:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 6))
				{
				return false;
				}
			
			for (j = 0; j < 6; j++)
				{
				fReferenceBlackWhite [j] = stream.TagValue_real64 (tagType);
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("ReferenceBlackWhite: %0.1f %0.1f %0.1f %0.1f %0.1f %0.1f\n",
						fReferenceBlackWhite [0],
						fReferenceBlackWhite [1],
						fReferenceBlackWhite [2],
						fReferenceBlackWhite [3],
						fReferenceBlackWhite [4],
						fReferenceBlackWhite [5]);

				}
				
			#endif
			
			break;
			
			}
			
		case tcCFARepeatPatternDim:
			{
			
			CheckCFA (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				{
				return false;
				}
			
			fCFARepeatPatternRows = stream.TagValue_uint32 (tagType);
			fCFARepeatPatternCols = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("CFARepeatPatternDim: Rows = %u, Cols = %u\n",
						(unsigned) fCFARepeatPatternRows,
						(unsigned) fCFARepeatPatternCols);
				
				}
				
			#endif
				
			break;
		
			}
			
		case tcCFAPattern:
			{
			
			CheckCFA (parentCode, tagCode, fPhotometricInterpretation);

			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				{
				return false;
				}
			
			if (!CheckTagCount (parentCode, tagCode, tagCount,
								SafeUint32Mult(fCFARepeatPatternRows, fCFARepeatPatternCols)))
				{
				return false;
				}
				
			if (fCFARepeatPatternRows < 1 || fCFARepeatPatternRows > kMaxCFAPattern ||
				fCFARepeatPatternCols < 1 || fCFARepeatPatternCols > kMaxCFAPattern)
				{
				return false;
				}
				
			// Note that the Exif spec stores this array in a different
			// scan order than the TIFF-EP spec.
			
			for (j = 0; j < fCFARepeatPatternRows; j++)
				for (k = 0; k < fCFARepeatPatternCols; k++)
					{
					
					fCFAPattern [j] [k] = stream.Get_uint8 ();
					
					}
					
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("CFAPattern:\n");
				
				for (j = 0; j < fCFARepeatPatternRows; j++)
					{
					
					int32 spaces = 4;
					
					for (k = 0; k < fCFARepeatPatternCols; k++)
						{
						
						while (spaces-- > 0)
							{
							printf (" ");
							}
							
						const char *name = LookupCFAColor (fCFAPattern [j] [k]);
						
						spaces = 9 - (int32) strlen (name);
						
						printf ("%s", name);
						
						}
						
					printf ("\n");
					
					}
					
				}
				
			#endif
			
			break;
			
			}
			
		case tcCFAPlaneColor:
			{
			
			CheckCFA (parentCode, tagCode, fPhotometricInterpretation);

			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				{
				return false;
				}
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 3, kMaxColorPlanes))
				{
				return false;
				}
				
			for (j = 0; j < kMaxColorPlanes; j++)
				{
				
				if (j < tagCount)
					fCFAPlaneColor [j] = stream.Get_uint8 ();
					
				else
					fCFAPlaneColor [j] = 255;
					
				}
				
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("CFAPlaneColor:");
				
				for (j = 0; j < tagCount; j++)
					{
					
					printf (" %s", LookupCFAColor (fCFAPlaneColor [j]));
					
					}
					
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcCFALayout:
			{
			
			CheckCFA (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCFALayout = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("CFALayout: %s\n",
						LookupCFALayout (fCFALayout));
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcLinearizationTable:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			fLinearizationTableType   = tagType;
			fLinearizationTableCount  = tagCount;
			fLinearizationTableOffset = tagOffset;
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				DumpTagValues (stream,
							   "Table",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);

				}
				
			#endif
			
			break;
			
			}
				
		case tcBlackLevelRepeatDim:
			{

			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				{
				return false;
				}
			
			fBlackLevelRepeatRows = stream.TagValue_uint32 (tagType);
			fBlackLevelRepeatCols = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("BlackLevelRepeatDim: Rows = %u, Cols = %u\n",
						(unsigned) fBlackLevelRepeatRows,
						(unsigned) fBlackLevelRepeatCols);
				
				}
				
			#endif
				
			break;
		
			}
			
		case tcBlackLevel:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);

			if (!CheckTagCount (parentCode, tagCode, tagCount, SafeUint32Mult(fBlackLevelRepeatRows,
															   fBlackLevelRepeatCols,
															   fSamplesPerPixel)))
				{
				return false;
				}
			
			if (fBlackLevelRepeatRows < 1 || fBlackLevelRepeatRows > kMaxBlackPattern   ||
				fBlackLevelRepeatCols < 1 || fBlackLevelRepeatCols > kMaxBlackPattern   ||
				fSamplesPerPixel      < 1 || fSamplesPerPixel      > kMaxSamplesPerPixel)
				{
				return false;
				}
				
			for (j = 0; j < fBlackLevelRepeatRows; j++)
				for (k = 0; k < fBlackLevelRepeatCols; k++)
					for (n = 0; n < fSamplesPerPixel; n++)
						{
						
						fBlackLevel [j] [k] [n] = stream.TagValue_real64 (tagType);
						
						}
						
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("BlackLevel:");
				
				if (fBlackLevelRepeatRows == 1 &&
					fBlackLevelRepeatCols == 1)
					{
					
					for (n = 0; n < fSamplesPerPixel; n++)
						{
						printf (" %0.2f", fBlackLevel [0] [0] [n]);
						}
						
					printf ("\n");
					
					}
					
				else
					{
					
					printf ("\n");
					
					for (n = 0; n < fSamplesPerPixel; n++)
						{
						
						if (fSamplesPerPixel > 1)
							{
							printf ("    Sample: %u\n", (unsigned) n);
							}
							
						for (j = 0; j < fBlackLevelRepeatRows; j++)
							{
							
							printf ("   ");
							
							for (k = 0; k < fBlackLevelRepeatCols; k++)
								{
								
								printf (" %8.2f", fBlackLevel [j] [k] [n]);
								
								}
								
							printf ("\n");
							
							}
						
						}
						
					}
				
				}
				
			#endif
				
			break;
			
			}

		case tcBlackLevelDeltaH:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			fBlackLevelDeltaHType   = tagType;
			fBlackLevelDeltaHCount  = tagCount;
			fBlackLevelDeltaHOffset = tagOffset;
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				DumpTagValues (stream,
							   "Delta",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);

				}
				
			#endif
			
			break;
			
			}
				
		case tcBlackLevelDeltaV:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			fBlackLevelDeltaVType   = tagType;
			fBlackLevelDeltaVCount  = tagCount;
			fBlackLevelDeltaVOffset = tagOffset;
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				DumpTagValues (stream,
							   "Delta",
							   parentCode,
							   tagCode,
							   tagType,
							   tagCount);

				}
				
			#endif
			
			break;
			
			}
				
		case tcWhiteLevel:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, fSamplesPerPixel))
				return false;
				
			for (j = 0; j < tagCount && j < kMaxSamplesPerPixel; j++)
				{

				fWhiteLevel [j] = stream.TagValue_real64 (tagType);

				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("WhiteLevel:");
				
				for (j = 0; j < tagCount && j < kMaxSamplesPerPixel; j++)
					{

					printf (" %0.0f", fWhiteLevel [j]);

					}
					
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcDefaultScale:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fDefaultScaleH = stream.TagValue_urational (tagType);
			fDefaultScaleV = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("DefaultScale: H = %0.4f V = %0.4f\n",
						fDefaultScaleH.As_real64 (),
						fDefaultScaleV.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcDefaultCropOrigin:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fDefaultCropOriginH = stream.TagValue_urational (tagType);
			fDefaultCropOriginV = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("DefaultCropOrigin: H = %0.2f V = %0.2f\n",
						fDefaultCropOriginH.As_real64 (),
						fDefaultCropOriginV.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcDefaultCropSize:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
				
			fDefaultCropSizeH = stream.TagValue_urational (tagType);
			fDefaultCropSizeV = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("DefaultCropSize: H = %0.2f V = %0.2f\n",
						fDefaultCropSizeH.As_real64 (),
						fDefaultCropSizeV.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcDefaultUserCrop:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 4))
				return false;
				
			fDefaultUserCropT = stream.TagValue_urational (tagType);
			fDefaultUserCropL = stream.TagValue_urational (tagType);
			fDefaultUserCropB = stream.TagValue_urational (tagType);
			fDefaultUserCropR = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("DefaultUserCrop: T = %0.2lf L = %0.2lf B = %0.2lf R = %0.2lf\n",
						(double) fDefaultUserCropT.As_real64 (),
						(double) fDefaultUserCropL.As_real64 (),
						(double) fDefaultUserCropB.As_real64 (),
						(double) fDefaultUserCropR.As_real64 ());
						
						
				}
				
			#endif	// qDNGValidate
			
			break;
			
			}
				
		case tcBayerGreenSplit:
			{
			
			CheckCFA (parentCode, tagCode, fPhotometricInterpretation);

			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBayerGreenSplit = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				printf ("BayerGreenSplit: %u\n", (unsigned) fBayerGreenSplit);
				}
				
			#endif
				
			break;
			
			}
				
		case tcChromaBlurRadius:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
				
			fChromaBlurRadius = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("ChromaBlurRadius: %0.2f\n",
						fChromaBlurRadius.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcAntiAliasStrength:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
				
			fAntiAliasStrength = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("AntiAliasStrength: %0.2f\n",
						fAntiAliasStrength.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcBestQualityScale:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
				
			fBestQualityScale = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("BestQualityScale: %0.4f\n",
						fBestQualityScale.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcActiveArea:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 4))
				return false;
				
			fActiveArea.t = stream.TagValue_int32 (tagType);
			fActiveArea.l = stream.TagValue_int32 (tagType);
			fActiveArea.b = stream.TagValue_int32 (tagType);
			fActiveArea.r = stream.TagValue_int32 (tagType);
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("ActiveArea: T = %d L = %d B = %d R = %d\n",
						(int) fActiveArea.t,
						(int) fActiveArea.l,
						(int) fActiveArea.b,
						(int) fActiveArea.r);
						
				}
				
			#endif
			
			break;
			
			}
				
		case tcMaskedAreas:
			{
			
			CheckMainIFD (parentCode, tagCode, fNewSubFileType);
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			uint32 rect_count = tagCount / 4;
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, rect_count * 4))
				return false;
				
			fMaskedAreaCount = rect_count;
				
			if (fMaskedAreaCount > kMaxMaskedAreas)
				fMaskedAreaCount = kMaxMaskedAreas;
				
			for (j = 0; j < fMaskedAreaCount; j++)
				{
				
				fMaskedArea [j].t = stream.TagValue_int32 (tagType);
				fMaskedArea [j].l = stream.TagValue_int32 (tagType);
				fMaskedArea [j].b = stream.TagValue_int32 (tagType);
				fMaskedArea [j].r = stream.TagValue_int32 (tagType);
				
				}
			
			#if qDNGValidate
				
			if (gVerbose)
				{
				
				printf ("MaskedAreas: %u\n", (unsigned) fMaskedAreaCount);
				
				for (j = 0; j < fMaskedAreaCount; j++)
					{
				
					printf ("    Area [%u]: T = %d L = %d B = %d R = %d\n",
							(unsigned) j,
							(int) fMaskedArea [j].t,
							(int) fMaskedArea [j].l,
							(int) fMaskedArea [j].b,
							(int) fMaskedArea [j].r);
							
					}
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcPreviewApplicationName:
			{

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

			#endif
			
			break;
			
			}
				
		case tcPreviewApplicationVersion:
			{

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

			#endif
			
			break;
			
			}
				
		case tcPreviewSettingsName:
			{

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

			#endif
			
			break;
			
			}
				
		case tcPreviewSettingsDigest:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
				return false;
				
			stream.Get (fPreviewInfo.fSettingsDigest.data, 16);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("PreviewSettingsDigest: ");
				
				DumpFingerprint (fPreviewInfo.fSettingsDigest);
									
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcPreviewColorSpace:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
				
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPreviewInfo.fColorSpace = (PreviewColorSpaceEnum)
									   stream.TagValue_uint32 (tagType);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("PreviewColorSpace: %s\n",
						LookupPreviewColorSpace ((uint32) fPreviewInfo.fColorSpace));
				
				}
				
			#endif
				
			break;
			
			}
				
		case tcPreviewDateTime:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fPreviewInfo.fDateTime,
							false);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("PreviewDateTime: ");
				
				DumpString (fPreviewInfo.fDateTime);
				
				printf ("\n");
				
				}

			#endif
			
			break;
			
			}
				
		case tcRowInterleaveFactor:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
				return false;
			
			fRowInterleaveFactor = stream.TagValue_uint32 (tagType);

			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("RowInterleaveFactor: %u\n",
						(unsigned) fRowInterleaveFactor);
				
				}

			#endif
			
			break;
			
			}
			
		case tcSubTileBlockSize:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
				return false;
			
			fSubTileBlockRows = stream.TagValue_uint32 (tagType);
			fSubTileBlockCols = stream.TagValue_uint32 (tagType);

			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("SubTileBlockSize: rows = %u, cols = %u\n",
						(unsigned) fSubTileBlockRows,
						(unsigned) fSubTileBlockCols);
				
				}

			#endif
			
			break;
			
			}
			
		case tcOpcodeList1:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fOpcodeList1Count  = tagCount;
			fOpcodeList1Offset = tagOffset;
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("OpcodeList1: count = %u, offset = %u\n",
						(unsigned) fOpcodeList1Count,
						(unsigned) fOpcodeList1Offset);
				
				}

			#endif
			
			break;
			
			}
				
		case tcOpcodeList2:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fOpcodeList2Count  = tagCount;
			fOpcodeList2Offset = tagOffset;
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("OpcodeList2: count = %u, offset = %u\n",
						(unsigned) fOpcodeList2Count,
						(unsigned) fOpcodeList2Offset);
				
				}

			#endif
			
			break;
			
			}
				
		case tcOpcodeList3:
			{
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			fOpcodeList3Count  = tagCount;
			fOpcodeList3Offset = tagOffset;
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("OpcodeList3: count = %u, offset = %u\n",
						(unsigned) fOpcodeList3Count,
						(unsigned) fOpcodeList3Offset);
				
				}

			#endif
			
			break;
			
			}
			
		case tcRawToPreviewGain:
			{
			
			#if qDNGValidate
						
			if (fNewSubFileType != sfPreviewImage)
				{
						
				char message [256];
				
				sprintf (message,
						 "%s %s is not allowed IFDs with NewSubFileType != PreviewImage",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			#endif
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
			
			CheckTagType (parentCode, tagCode, tagType, ttDouble);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
				return false;
				
			fPreviewInfo.fRawToPreviewGain = stream.TagValue_real64 (tagType);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("RawToPreviewGain = %f\n",
						fPreviewInfo.fRawToPreviewGain);
				
				}

			#endif
			
			break;
			
			}
				
		case tcCacheVersion:
			{
			
			#if qDNGValidate
						
			if (fNewSubFileType != sfPreviewImage)
				{
						
				char message [256];
				
				sprintf (message,
						 "%s %s is not allowed IFDs with NewSubFileType != PreviewImage",
						 LookupParentCode (parentCode),
						 LookupTagCode (parentCode, tagCode));
						 
				ReportWarning (message);
							 
				}
				
			#endif
			
			CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
				return false;
				
			fPreviewInfo.fCacheVersion = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
						
			if (gVerbose)
				{
				
				printf ("CacheVersion = 0x%x\n",
						(unsigned) fPreviewInfo.fCacheVersion);
				
				}

			#endif
			
			break;
			
			}
				
		default:
			{
			
			return false;
			
			}

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

void dng_ifd::PostParse ()
	{
	
	uint32 j;
	uint32 k;
	
	// There is only one PlanarConfiguration for single sample imaages.
	
	if (fSamplesPerPixel == 1)
		{
		fPlanarConfiguration = pcInterleaved;
		}
		
	// Default tile size.
		
	if (fTileWidth == 0)
		{
		fTileWidth = fImageWidth;
		}
		
	if (fTileLength == 0)
		{
		fTileLength = fImageLength;
		}
		
	// Default ActiveArea.
		
	dng_rect imageArea (0, 0, fImageLength, fImageWidth);

	if (fActiveArea.IsZero ())
		{
		fActiveArea = imageArea;
		}
		
	// Default crop size.
		
	if (fDefaultCropSizeH.d == 0)
		{
		fDefaultCropSizeH = dng_urational (fActiveArea.W (), 1);
		}
		
	if (fDefaultCropSizeV.d == 0)
		{
		fDefaultCropSizeV = dng_urational (fActiveArea.H (), 1);
		}
		
	// Default white level.
		
	uint32 defaultWhite = (fSampleFormat [0] == sfFloatingPoint) ?
						  1 :
						  (uint32) ((((uint64) 1) << fBitsPerSample [0]) - 1);
						
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		
		if (fWhiteLevel [j] < 0.0)
			{
			fWhiteLevel [j] = (real64) defaultWhite;
			}
		
		}
		
	// Check AntiAliasStrength.

	if (fAntiAliasStrength.As_real64 () < 0.0 ||
		fAntiAliasStrength.As_real64 () > 1.0)
		{
		
		#if qDNGValidate
		
		ReportWarning ("Invalid AntiAliasStrength");
					 
		#endif
					 
		fAntiAliasStrength = dng_urational (1, 1);
					 
		}
		
	// Check MaskedAreas.
		
	for (j = 0; j < fMaskedAreaCount; j++)
		{
		
		const dng_rect &r = fMaskedArea [j];
		
		if (r.IsEmpty () || ((r & imageArea) != r))
			{
			
			#if qDNGValidate
		
			ReportWarning ("Invalid MaskedArea");
						 
			#endif
						 
			fMaskedAreaCount = 0;
			
			break;
					 
			}
		
		if ((r & fActiveArea).NotEmpty ())
			{
				
			#if qDNGValidate

			ReportWarning ("MaskedArea overlaps ActiveArea");
						 
			#endif
					 
			fMaskedAreaCount = 0;
			
			break;
					 
			}
		
		for (k = 0; k < j; k++)
			{
			
			if ((r & fMaskedArea [k]).NotEmpty ())
				{
				
				#if qDNGValidate

				ReportWarning ("MaskedAreas overlap each other");
							 
				#endif
						 
				fMaskedAreaCount = 0;
				
				break;
						 
				}

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

bool dng_ifd::IsValidCFA (dng_shared &shared,
						  uint32 parentCode)
	{
	
	uint32 j;
	uint32 k;
	uint32 n;
	
	#if !qDNGValidate
	
	(void) parentCode;			// Unused
	
	#endif
	
	if (fCFARepeatPatternRows < 1 || fCFARepeatPatternRows > kMaxCFAPattern ||
		fCFARepeatPatternCols < 1 || fCFARepeatPatternCols > kMaxCFAPattern)
		{
		
		#if qDNGValidate
		
		ReportError ("Missing or invalid CFAPatternRepeatDim",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
					 
		}
		
	uint32 count [kMaxColorPlanes];
	
	for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
		{
		count [n] = 0;
		}
		
	for (j = 0; j < fCFARepeatPatternRows; j++)
		{
		
		for (k = 0; k < fCFARepeatPatternCols; k++)
			{
			
			bool found = false;
			
			for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
				{
				
				if (fCFAPattern [j] [k] == fCFAPlaneColor [n])
					{
					found = true;
					count [n] ++;
					break;
					}
				
				}
				
			if (!found)
				{
				
				#if qDNGValidate
		
				ReportError ("CFAPattern contains colors not included in the CFAPlaneColor tag",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
					 
				}
			
			}
			
		}
	
	for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
		{
		
		if (count [n] == 0)
			{
			
			#if qDNGValidate
		
			ReportError ("CFAPattern does not contain all the colors in the CFAPlaneColor tag",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;

			}
	
		}
		
	if (fCFALayout < 1 || fCFALayout > 9)
		{
		
		#if qDNGValidate
		
		ReportError ("Invalid CFALayout",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;

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

bool dng_ifd::IsValidDNG (dng_shared &shared,
					      uint32 parentCode)
	{
	
	uint32 j;
		
	bool isFloatingPoint = (fSampleFormat [0] == sfFloatingPoint);
	
	dng_rect imageArea (0, 0, fImageLength, fImageWidth);
	
	uint32 defaultWhite = isFloatingPoint ?
						  1 :
						  (uint32) ((((uint64) 1) << fBitsPerSample [0]) - 1);
						
	bool isMonochrome = (shared.fCameraProfile.fColorPlanes == 1);
	bool isColor      = !isMonochrome;
		
	bool isMainIFD = (fNewSubFileType == sfMainImage);
	
	// Check NewSubFileType.
	
	if (!fUsesNewSubFileType)
		{
		
		#if qDNGValidate
		
		ReportError ("Missing NewSubFileType",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	if (fNewSubFileType != sfMainImage		  &&
		fNewSubFileType != sfPreviewImage	  &&
		fNewSubFileType != sfTransparencyMask &&
		fNewSubFileType != sfPreviewMask      &&
		fNewSubFileType != sfAltPreviewImage)
		{
		
		#if qDNGValidate
		
		ReportError ("Unexpected NewSubFileType",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check ImageWidth and ImageLength.
			
	if (fImageWidth < 1)
		{
		
		#if qDNGValidate
		
		ReportError ("Missing or invalid ImageWidth",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
	
	if (fImageLength < 1)
		{
		
		#if qDNGValidate
		
		ReportError ("Missing or invalid ImageLength",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	if (fImageWidth  > kMaxImageSide ||
		fImageLength > kMaxImageSide)
		{
		
		#if qDNGValidate
		
		ReportWarning ("Image size is larger than supported");
					 
		#endif
					 
		return false;
		
		}
	
	// Check PhotometricInterpretation.
	
	if (fNewSubFileType == sfTransparencyMask ||
		fNewSubFileType == sfPreviewMask)
		{
		
		if (fPhotometricInterpretation != piTransparencyMask)
			{
			
			#if qDNGValidate
	
			ReportError ("NewSubFileType requires PhotometricInterpretation = TransparencyMask",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		}
		
	else
		{
		
		switch (fPhotometricInterpretation)
			{
			
			case piBlackIsZero:
			case piRGB:
			case piYCbCr:
				{
				
				if (isMainIFD)
					{
					
					#if qDNGValidate
			
					ReportError ("PhotometricInterpretation requires NewSubFileType = 1",
								 LookupParentCode (parentCode));
								 
					#endif
								 
					return false;
								 
					}
					
				break;
				
				}
				
			case piCFA:
				{
				
				if (!isMainIFD)
					{
					
					#if qDNGValidate
			
					ReportError ("PhotometricInterpretation requires NewSubFileType = 0",
								 LookupParentCode (parentCode));
								 
					#endif
								 
					return false;
								 
					}
					
				break;
				
				}
				
			case piLinearRaw:
				break;
				
			default:
				{
					
				#if qDNGValidate
			
				ReportError ("Missing or invalid PhotometricInterpretation",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;

				}
				
			}
			
		}
		
	switch (fPhotometricInterpretation)
		{
		
		case piBlackIsZero:
			{
			
			// Allow black in white previews even in color images since the
			// raw processing software may be converting to grayscale.
			
			if (isColor && isMainIFD)
				{
				
				#if qDNGValidate
		
				ReportError ("PhotometricInterpretation forbids use of ColorMatrix1 tag",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;

				}
				
			break;
			
			}
			
		case piRGB:
		case piYCbCr:
			{
			
			// Allow color previews even in monochrome DNG files, since the
			// raw procesing software may be adding color effects.
			
			break;
			
			}

		case piCFA:
			{
			
			if (isMonochrome)
				{
				
				#if qDNGValidate
		
				ReportError ("PhotometricInterpretation requires use of ColorMatrix1 tag",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;

				}
				
			break;
			
			}
			
		}
		
	if (isFloatingPoint)
		{
		
		if (fPhotometricInterpretation != piCFA &&
			fPhotometricInterpretation != piLinearRaw &&
			fPhotometricInterpretation != piTransparencyMask)
			{
			
			#if qDNGValidate
	
			ReportError ("Floating point data requires PhotometricInterpretation CFA or LinearRaw or TransparencyMask",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;

			}
		
		}
		
	// Check SamplesPerPixel and BitsPerSample.
		
	uint32 minSamplesPerPixel = 1;
	uint32 maxSamplesPerPixel = 1;
	
	uint32 minBitsPerSample = 8;
	uint32 maxBitsPerSample = 16;
	
	switch (fPhotometricInterpretation)
		{
		
		case piBlackIsZero:
			break;
			
		case piRGB:
		case piYCbCr:
			{
			minSamplesPerPixel = 3;
			maxSamplesPerPixel = 3;
			break;
			}
			
		case piCFA:
			{
			maxSamplesPerPixel = kMaxSamplesPerPixel;
			maxBitsPerSample   = 32;
			break;
			}
			
		case piLinearRaw:
			{
			minSamplesPerPixel = shared.fCameraProfile.fColorPlanes;
			maxSamplesPerPixel = shared.fCameraProfile.fColorPlanes;
			maxBitsPerSample   = 32;
			break;
			}
			
		case piTransparencyMask:
			{
			minBitsPerSample = 8;
			maxBitsPerSample = 16;
			break;
			}
			
		}
		
	if (isFloatingPoint)
		{
		minBitsPerSample = 16;
		maxBitsPerSample = 32;
		}
		
	if (fSamplesPerPixel < minSamplesPerPixel ||
		fSamplesPerPixel > maxSamplesPerPixel)
		{

		#if qDNGValidate
		
		ReportError ("Missing or invalid SamplesPerPixel",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;

		}
		
	for (j = 0; j < kMaxSamplesPerPixel; j++)
		{
		
		if (j < fSamplesPerPixel)
			{
			
			if (fBitsPerSample [j] < minBitsPerSample ||
				fBitsPerSample [j] > maxBitsPerSample)
				{
				
				#if qDNGValidate
		
				ReportError ("Missing or invalid BitsPerSample",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
							 
				}
				
			if (isFloatingPoint &&
				fBitsPerSample [j] != 16 &&
				fBitsPerSample [j] != 24 &&
				fBitsPerSample [j] != 32)
				{
				
				#if qDNGValidate
		
				ReportError ("Invalid BitsPerSample for floating point",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
							 
				}
				
			if (minBitsPerSample   ==  8 &&
				maxBitsPerSample   == 16 &&
				fBitsPerSample [j] !=  8 &&
				fBitsPerSample [j] != 16)
				{
				
				#if qDNGValidate
		
				ReportError ("Rendered previews and integer masks require 8 or 16 bits per sample",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
							 
				}
				
			if (j > 0 && fBitsPerSample [j] != fBitsPerSample [0])
				{
				
				#if qDNGValidate

				ReportError ("BitsPerSample not equal for all samples",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
		
				}
			
			}
			
		else
			{
			
			if (fBitsPerSample [j] != 0)
				{
				
				#if qDNGValidate

				ReportError ("Too many values specified in BitsPerSample",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
		
				}
			
			}
			
		}
		
	// Check Compression.
		
	switch (fCompression)
		{
		
		case ccUncompressed:
			break;
			
		case ccJPEG:
			{
			
			if (fPhotometricInterpretation == piRGB)
				{
				
				#if qDNGValidate

				ReportError ("JPEG previews should use PhotometricInterpretation = YCbYb",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
						 
				}
				
			if (fBitsPerSample [0] > 16)
				{
				
				#if qDNGValidate

				ReportError ("JPEG compression is limited to 16 bits/sample",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
						 
				}
			
			break;
			
			}
			
		case ccLossyJPEG:
			{
			
			if (fPhotometricInterpretation != piLinearRaw)
				{
				
				#if qDNGValidate

				ReportError ("Lossy JPEG compression code requires PhotometricInterpretation = LinearRaw",
							 LookupParentCode (parentCode));
							 
				#endif

				return false;
				
				}
			
			if (fBitsPerSample [0] != 8)
				{
				
				#if qDNGValidate

				ReportError ("Lossy JPEG compression is limited to 8 bits/sample",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				return false;
						 
				}
				
			break;
			
			}
			
		case ccDeflate:
			{
			
			if (!isFloatingPoint &&
				fBitsPerSample [0] != 32 &&
				fPhotometricInterpretation != piTransparencyMask)
				{
				
				#if qDNGValidate

				ReportError ("ZIP compression is limited to floating point and 32-bit integer and transparency masks",
							 LookupParentCode (parentCode));
							 
				#endif
							 
				}
						
			break;
			
			}
			
		default:
			{
			
			#if qDNGValidate

			ReportError ("Unsupported Compression",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
		
			}
			
		}
		
	// Check Predictor.
	
	if (isFloatingPoint && fCompression == ccDeflate &&
				(fPredictor == cpFloatingPoint   ||
				 fPredictor == cpFloatingPointX2 ||
				 fPredictor == cpFloatingPointX4))
		{
		
		// These combinations are supported.
		
		}
	
	else if (!isFloatingPoint && fCompression == ccDeflate &&
				(fPredictor == cpHorizontalDifference   ||
				 fPredictor == cpHorizontalDifferenceX2 ||
				 fPredictor == cpHorizontalDifferenceX4))
		{
		
		// These combinations are supported.
		
		}
	
	else if (fPredictor != cpNullPredictor)
		{
		
		#if qDNGValidate

		ReportError ("Unsupported Predictor",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check FillOrder.
		
	if (fFillOrder != 1)
		{
		
		#if qDNGValidate

		ReportError ("Unsupported FillOrder",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check PlanarConfiguration.
		
	if (fPlanarConfiguration != pcInterleaved)
		{
		
		#if qDNGValidate

		ReportError ("Unsupported PlanarConfiguration",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check ExtraSamples.
	
	if (fExtraSamplesCount != 0)
		{
		
		#if qDNGValidate

		ReportError ("Unsupported ExtraSamples",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check SampleFormat.
	
	for (j = 0; j < fSamplesPerPixel; j++)
		{
	
		if (fSampleFormat [j] != (isFloatingPoint ? sfFloatingPoint : sfUnsignedInteger))
			{
			
			#if qDNGValidate

			ReportError ("Unsupported SampleFormat",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
			
		}
		
	// Check Orientation.
		
	if (fOrientation > 9)
		{
		
		#if qDNGValidate

		ReportError ("Unknown Orientation",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;

		}
		
	#if qDNGValidate

	if (fOrientation != 0 && parentCode != 0)
		{
		
		ReportWarning ("Unexpected Orientation tag",
					   LookupParentCode (parentCode));
		
		}
		
	if (fOrientation == 0 && parentCode == 0)
		{
		
		ReportWarning ("Missing Orientation tag",
					   LookupParentCode (parentCode));
		
		}
		
	#endif
	
	// Check Strips vs. Tiles.
		
	if (!fUsesStrips && !fUsesTiles)
		{
		
		#if qDNGValidate

		ReportError ("IFD uses neither strips nor tiles",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
	
	if (fUsesStrips && fUsesTiles)
		{
		
		#if qDNGValidate

		ReportError ("IFD uses both strips and tiles",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check tile info.
		
	uint32 tilesWide = SafeUint32DivideUp(fImageWidth, fTileWidth);
	uint32 tilesHigh = SafeUint32DivideUp(fImageLength, fTileLength);
	
	uint32 tileCount = tilesWide * tilesHigh;
	
	if (fTileOffsetsCount != tileCount)
		{
		
		#if qDNGValidate

		ReportError ("Missing or invalid Strip/TileOffsets",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	if (fTileByteCountsCount != tileCount)
		{
		
		#if qDNGValidate

		ReportError ("Missing or invalid Strip/TileByteCounts",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
		
		}
		
	// Check CFA pattern.
		
	if (fPhotometricInterpretation == piCFA)
		{
		
		if (!IsValidCFA (shared, parentCode))
			{
			
			return false;
			
			}
		
		}
		
	// Check ActiveArea.
		
	if (((fActiveArea & imageArea) != fActiveArea) || fActiveArea.IsEmpty ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid ActiveArea",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
			
		}
		
	if (fActiveArea != imageArea)
		{
		
		if (shared.fDNGBackwardVersion < dngVersion_1_1_0_0)
			{
			
			#if qDNGValidate

			ReportError ("Non-default ActiveArea tag not allowed in this DNG version",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		}
		
	// Check LinearizationTable.
		
	if (fLinearizationTableCount)
		{
		
		if (fLinearizationTableType != ttShort)
			{
			
			#if qDNGValidate

			ReportError ("Invalidate LinearizationTable type",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		if (fLinearizationTableCount < 2 ||
			fLinearizationTableCount > 65536)
			{
			
			#if qDNGValidate

			ReportError ("Invalidate LinearizationTable count",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
			
		if (isFloatingPoint || fBitsPerSample [0] > 16)
			{
			
			#if qDNGValidate

			ReportError ("Linearization table not allowed for this data type",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		}
		
	// Check BlackLevelRepeatDim.
		
	if (fBlackLevelRepeatRows < 1 || fBlackLevelRepeatRows > kMaxBlackPattern ||
		fBlackLevelRepeatCols < 1 || fBlackLevelRepeatCols > kMaxBlackPattern)
		{
		
		#if qDNGValidate

		ReportError ("Invalid BlackLevelRepeatDim",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
					 
		}
		
	// Check BlackLevelDeltaH.
		
	if (fBlackLevelDeltaHCount != 0 &&
		fBlackLevelDeltaHCount != fActiveArea.W ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid BlackLevelDeltaH count",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
					 
		}
		
	// Check BlackLevelDeltaV.
		
	if (fBlackLevelDeltaVCount != 0 &&
		fBlackLevelDeltaVCount != fActiveArea.H ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid BlackLevelDeltaV count",
					 LookupParentCode (parentCode));
					 
		#endif
					 
		return false;
					 
		}
		
	// Check WhiteLevel.
	
	real64 maxWhite = fLinearizationTableCount ? 65535.0
											   : (real64) defaultWhite;
		
	for (j = 0; j < fSamplesPerPixel; j++)
		{
		
		if (fWhiteLevel [j] < 1.0 || (fWhiteLevel [j] > maxWhite && !isFloatingPoint))
			{
			
			#if qDNGValidate

			ReportError ("Invalid WhiteLevel",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
					 
			}
			
		}

	// Check BlackLevel.

	for (j = 0; j < kMaxBlackPattern; j++)
		{
		
		for (uint32 k = 0; k < kMaxBlackPattern; k++)
			{
			
			for (uint32 s = 0; s < kMaxSamplesPerPixel; s++)
				{

				const real64 black = fBlackLevel [j][k][s];

				if (black >= fWhiteLevel [s])
					{
					
					#if qDNGValidate

					ReportError ("Invalid BlackLevel",
								 LookupParentCode (parentCode));

					#endif

					return false;
					
					}
				
				}
			
			}
		
		}
		
	// Check DefaultScale.
		
	if (fDefaultScaleH.As_real64 () <= 0.0 ||
		fDefaultScaleV.As_real64 () <= 0.0)
		{
			
		#if qDNGValidate

		ReportError ("Invalid DefaultScale");
					 
		#endif
					 
		return false;

		}
		
	// Check BestQualityScale.
		
	if (fBestQualityScale.As_real64 () < 1.0)
		{
		
		#if qDNGValidate

		ReportError ("Invalid BestQualityScale");
					 
		#endif
					 
		return false;

		}
		
	// Check DefaultCropOrigin.
		
	if (fDefaultCropOriginH.As_real64 () < 0.0 ||
		fDefaultCropOriginV.As_real64 () < 0.0 ||
		fDefaultCropOriginH.As_real64 () >= (real64) fActiveArea.W () ||
		fDefaultCropOriginV.As_real64 () >= (real64) fActiveArea.H ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid DefaultCropOrigin");
					 
		#endif
					 
		return false;

		}
		
	// Check DefaultCropSize.
		
	if (fDefaultCropSizeH.As_real64 () <= 0.0 					   ||
		fDefaultCropSizeV.As_real64 () <= 0.0 					   ||
		fDefaultCropSizeH.As_real64 () > (real64) fActiveArea.W () ||
		fDefaultCropSizeV.As_real64 () > (real64) fActiveArea.H ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid DefaultCropSize");
					 
		#endif
					 
		return false;

		}
		
	// Check DefaultCrop area.
		
	if (fDefaultCropOriginH.As_real64 () +
		fDefaultCropSizeH  .As_real64 () > (real64) fActiveArea.W () ||
		fDefaultCropOriginV.As_real64 () +
		fDefaultCropSizeV  .As_real64 () > (real64) fActiveArea.H ())
		{
		
		#if qDNGValidate

		ReportError ("Default crop extends outside ActiveArea");
					 
		#endif
					 
		return false;

		}
		
	// Check DefaultUserCrop.
		
	if (fDefaultUserCropT.As_real64 () < 0.0 ||
		fDefaultUserCropL.As_real64 () < 0.0 ||
		fDefaultUserCropB.As_real64 () > 1.0 ||
		fDefaultUserCropR.As_real64 () > 1.0 ||
		fDefaultUserCropT.As_real64 () >= fDefaultUserCropB.As_real64 () ||
		fDefaultUserCropL.As_real64 () >= fDefaultUserCropR.As_real64 ())
		{
		
		#if qDNGValidate

		ReportError ("Invalid DefaultUserCrop");
					 
		#endif	// qDNGValidate
					 
		return false;

		}
		
	// The default crop and default user crop tags are not allowed for the
	// non-main image. If they are there, at least require that they be NOPs.
	
	if (!isMainIFD)
		{
		
		if (Round_int32 (fDefaultCropOriginH.As_real64 ()) != 0 ||
			Round_int32 (fDefaultCropOriginV.As_real64 ()) != 0)
			{
			
			#if qDNGValidate

			ReportError ("non-default DefaultCropOrigin on non-main image");
						 
			#endif
						 
			return false;

			}
		
		if (Round_int32 (fDefaultCropSizeH.As_real64 ()) != (int32) fImageWidth ||
			Round_int32 (fDefaultCropSizeV.As_real64 ()) != (int32) fImageLength)
			{
			
			#if qDNGValidate

			ReportError ("non-default DefaultCropSize on non-main image");
						 
			#endif
						 
			return false;

			}

		if (fDefaultUserCropT.As_real64 () != 0.0 ||
			fDefaultUserCropL.As_real64 () != 0.0 ||
			fDefaultUserCropB.As_real64 () != 1.0 ||
			fDefaultUserCropR.As_real64 () != 1.0)
			{
			
			#if qDNGValidate

			ReportError ("non-default DefaultCUserCrop on non-main image");
						 
			#endif	// qDNGValidate
						 
			return false;
			
			}

		}
		
	// Warning if too little padding on CFA image.
	
	#if qDNGValidate
	
	if (fPhotometricInterpretation == piCFA)
		{
		
		const real64 kMinPad = 1.9;
		
		if (fDefaultCropOriginH.As_real64 () < kMinPad)
			{
			
			ReportWarning ("Too little padding on left edge of CFA image",
						   "possible interpolation artifacts");
					 
			}
		
		if (fDefaultCropOriginV.As_real64 () < kMinPad)
			{
			
			ReportWarning ("Too little padding on top edge of CFA image",
						   "possible interpolation artifacts");
					 
			}
		
		if (fDefaultCropOriginH.As_real64 () +
			fDefaultCropSizeH  .As_real64 () > (real64) fActiveArea.W () - kMinPad)
			{
			
			ReportWarning ("Too little padding on right edge of CFA image",
						   "possible interpolation artifacts");
					 
			}
		
		if (fDefaultCropOriginV.As_real64 () +
			fDefaultCropSizeV  .As_real64 () > (real64) fActiveArea.H () - kMinPad)
			{
			
			ReportWarning ("Too little padding on bottom edge of CFA image",
						   "possible interpolation artifacts");
					 
			}
		
		}
	
	#endif
	
	// Check RowInterleaveFactor
		
	if (fRowInterleaveFactor != 1)
		{
		
		if (fRowInterleaveFactor < 1 ||
			fRowInterleaveFactor > fImageLength)
			{

			#if qDNGValidate

			ReportError ("RowInterleaveFactor out of valid range",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		if (shared.fDNGBackwardVersion < dngVersion_1_2_0_0)
			{
			
			#if qDNGValidate

			ReportError ("Non-default RowInterleaveFactor tag not allowed in this DNG version",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		}
		
	// Check SubTileBlockSize
	
	if (fSubTileBlockRows != 1 || fSubTileBlockCols != 1)
		{
		
		if (fSubTileBlockRows < 2 || fSubTileBlockRows > fTileLength ||
			fSubTileBlockCols < 1 || fSubTileBlockCols > fTileWidth)
			{
			
			#if qDNGValidate

			ReportError ("SubTileBlockSize out of valid range",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
			
		if ((fTileLength % fSubTileBlockRows) != 0 ||
			(fTileWidth  % fSubTileBlockCols) != 0)
			{
			
			#if qDNGValidate

			ReportError ("TileSize not exact multiple of SubTileBlockSize",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		if (shared.fDNGBackwardVersion < dngVersion_1_2_0_0)
			{
			
			#if qDNGValidate

			ReportError ("Non-default SubTileBlockSize tag not allowed in this DNG version",
						 LookupParentCode (parentCode));
						 
			#endif
						 
			return false;
			
			}
		
		}
		
	return true;
	
	}
							   
/*****************************************************************************/

uint32 dng_ifd::TilesAcross () const
	{
	
	if (fTileWidth)
		{

		return (fImageWidth + fTileWidth - 1) / fTileWidth;
		
		}
		
	return 0;

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

uint32 dng_ifd::TilesDown () const
	{
	
	if (fTileLength)
		{

		return (fImageLength + fTileLength - 1) / fTileLength;
		
		}
		
	return 0;

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

uint32 dng_ifd::TilesPerImage () const
	{
	
	uint32 total = TilesAcross () * TilesDown ();
	
	if (fPlanarConfiguration == pcPlanar)
		{
		
		total *= fSamplesPerPixel;
		
		}
		
	return total;
	
	}
		
/*****************************************************************************/

dng_rect dng_ifd::TileArea (uint32 rowIndex,
						    uint32 colIndex) const
	{
	
	dng_rect r;
	
	r.t = rowIndex * fTileLength;
	r.b = r.t      + fTileLength;
	
	r.l = colIndex * fTileWidth;
	r.r = r.l      + fTileWidth;
	
	// If this IFD is using strips rather than tiles, the last strip
	// is trimmed so it does not extend beyond the end of the image.
	
	if (fUsesStrips)
		{
		
		r.b = Min_uint32 (r.b, fImageLength);
		
		}
		
	return r;
	
	}
			
/*****************************************************************************/

uint32 dng_ifd::TileByteCount (const dng_rect &tile) const
	{
	
	if (fCompression == ccUncompressed)
		{
		
		uint32 bitsPerRow = SafeUint32Mult(tile.W (), fBitsPerSample [0]);
							
		if (fPlanarConfiguration == pcInterleaved)
			{
			
			bitsPerRow = SafeUint32Mult(bitsPerRow, fSamplesPerPixel);
			
			}
							
		uint32 bytesPerRow = SafeUint32DivideUp(bitsPerRow, 8);
		
		if (fPlanarConfiguration == pcRowInterleaved)
			{
			
			bytesPerRow = SafeUint32Mult(bytesPerRow, fSamplesPerPixel);
			
			}
		
		return SafeUint32Mult(bytesPerRow, tile.H ());
		
		}

	return 0;
	
	}
		
/*****************************************************************************/

void dng_ifd::SetSingleStrip ()
	{
	
	fTileWidth  = fImageWidth;
	fTileLength = fImageLength;
	
	fUsesTiles  = false;
	fUsesStrips = true;
	
	}
		
/*****************************************************************************/

void dng_ifd::FindTileSize (uint32 bytesPerTile,
						    uint32 cellH,
						    uint32 cellV)
	{
	
	uint32 bytesPerSample = fSamplesPerPixel *
							((fBitsPerSample [0] + 7) >> 3);
								
	uint32 samplesPerTile = bytesPerTile / bytesPerSample;
	
	uint32 tileSide = Round_uint32 (sqrt ((real64) samplesPerTile));
	
	fTileWidth = Min_uint32 (fImageWidth, tileSide);
		
	uint32 across = TilesAcross ();
								 
	fTileWidth = (fImageWidth + across - 1) / across;
	
	fTileWidth = ((fTileWidth + cellH - 1) / cellH) * cellH;
		
	fTileLength = Pin_uint32 (1,
						      samplesPerTile / fTileWidth,
						      fImageLength);
								  
	uint32 down = TilesDown ();
								 
	fTileLength = (fImageLength + down - 1) / down;
		
	fTileLength = ((fTileLength + cellV - 1) / cellV) * cellV;
	
	fUsesTiles  = true;
	fUsesStrips = false;
		
	}
		
/*****************************************************************************/

void dng_ifd::FindStripSize (uint32 bytesPerStrip,
						     uint32 cellV)
	{
	
	uint32 bytesPerSample = fSamplesPerPixel *
							((fBitsPerSample [0] + 7) >> 3);
								
	uint32 samplesPerStrip = bytesPerStrip / bytesPerSample;
	
	fTileWidth = fImageWidth;
		
	fTileLength = Pin_uint32 (1,
						      samplesPerStrip / fTileWidth,
						      fImageLength);
								  
	uint32 down = TilesDown ();
								 
	fTileLength = (fImageLength + down - 1) / down;
		
	fTileLength = ((fTileLength + cellV - 1) / cellV) * cellV;
		
	fUsesTiles  = false;
	fUsesStrips = true;
		
	}
		
/*****************************************************************************/

uint32 dng_ifd::PixelType () const
	{
	
	if (fSampleFormat [0] == sfFloatingPoint)
		{
		return ttFloat;
		}
	
	if (fBitsPerSample [0] <= 8)
		{
		return ttByte;
		}
		
	else if (fBitsPerSample [0] <= 16)
		{
		return ttShort;
		}
		
	return ttLong;
	
	}
		
/*****************************************************************************/

bool dng_ifd::IsBaselineJPEG () const
	{
	
	if (fBitsPerSample [0] != 8)
		{
		return false;
		}
		
	if (fSampleFormat [0] != sfUnsignedInteger)
		{
		return false;
		}

	if (fCompression == ccLossyJPEG)
		{
		return true;
		}
	
	if (fCompression != ccJPEG)
		{
		return false;
		}
		
	switch (fPhotometricInterpretation)
		{
		
		case piBlackIsZero:
			{
			return (fSamplesPerPixel == 1);
			}
			
		case piYCbCr:
			{
			return (fSamplesPerPixel     == 3            ) &&
				   (fPlanarConfiguration == pcInterleaved);
			}
			
		default:
			break;
			
		}
		
	return false;
	
	}
		
/*****************************************************************************/

bool dng_ifd::CanRead () const
	{
	
	dng_read_image reader;
	
	return reader.CanRead (*this);
	
	}
		
/*****************************************************************************/

void dng_ifd::ReadImage (dng_host &host,
						 dng_stream &stream,
						 dng_image &image,
						 dng_jpeg_image *jpegImage,
						 dng_fingerprint *jpegDigest) const
	{
	
	dng_read_image reader;
	
	reader.Read (host,
				 *this,
				 stream,
				 image,
				 jpegImage,
				 jpegDigest);
					 
	}
			
/*****************************************************************************/