/*****************************************************************************/
// 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_exif.cpp#1 $ */ 
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */

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

#include "dng_exif.h"

#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_parse_utils.h"
#include "dng_globals.h"
#include "dng_exceptions.h"
#include "dng_tag_values.h"
#include "dng_utils.h"

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

dng_exif::dng_exif ()

	:	fImageDescription ()
	,	fMake             ()
	,	fModel            ()
	,	fSoftware         ()
	,	fArtist           ()
	,	fCopyright        ()
	,	fCopyright2       ()
	,	fUserComment      ()

	,	fDateTime            ()
	,	fDateTimeStorageInfo ()
	
	,	fDateTimeOriginal  ()
	,	fDateTimeOriginalStorageInfo ()
	
	,	fDateTimeDigitized ()
	,	fDateTimeDigitizedStorageInfo ()
		
	,	fTIFF_EP_StandardID (0)
	,	fExifVersion        (0)
	,	fFlashPixVersion	(0)
	
	,	fExposureTime      ()
	,	fFNumber           ()
	,	fShutterSpeedValue ()
	,	fApertureValue     ()
	,	fBrightnessValue   ()
	,	fExposureBiasValue ()
	,	fMaxApertureValue  ()
	,	fFocalLength       ()
	,	fDigitalZoomRatio  ()
	,	fExposureIndex     ()
	,	fSubjectDistance   ()
	,	fGamma             ()
		
	,	fBatteryLevelR ()
	,	fBatteryLevelA ()
		
	,	fExposureProgram  	  (0xFFFFFFFF)
	,	fMeteringMode     	  (0xFFFFFFFF)
	,	fLightSource      	  (0xFFFFFFFF)
	,	fFlash			  	  (0xFFFFFFFF)
	,	fFlashMask 			  (0x0000FFFF)
	,	fSensingMethod    	  (0xFFFFFFFF)
	,	fColorSpace       	  (0xFFFFFFFF)
	,	fFileSource       	  (0xFFFFFFFF)
	,	fSceneType		  	  (0xFFFFFFFF)
	,	fCustomRendered   	  (0xFFFFFFFF)
	,	fExposureMode	  	  (0xFFFFFFFF)
	,	fWhiteBalance     	  (0xFFFFFFFF)
	,	fSceneCaptureType 	  (0xFFFFFFFF)
	,	fGainControl 		  (0xFFFFFFFF)
	,	fContrast 			  (0xFFFFFFFF)
	,	fSaturation 		  (0xFFFFFFFF)
	,	fSharpness 			  (0xFFFFFFFF)
	,	fSubjectDistanceRange (0xFFFFFFFF)
	,	fSelfTimerMode		  (0xFFFFFFFF)
	,	fImageNumber		  (0xFFFFFFFF)
	
	,	fFocalLengthIn35mmFilm (0)

	,	fSensitivityType		   (0)
	,	fStandardOutputSensitivity (0)
	,	fRecommendedExposureIndex  (0)
	,	fISOSpeed				   (0)
	,	fISOSpeedLatitudeyyy	   (0)
	,	fISOSpeedLatitudezzz	   (0)

	,	fSubjectAreaCount (0)
	
	,	fComponentsConfiguration (0)
	
	,	fCompresssedBitsPerPixel ()
	
	,	fPixelXDimension (0)
	,	fPixelYDimension (0)
	
	,	fFocalPlaneXResolution ()
	,	fFocalPlaneYResolution ()
		
	,	fFocalPlaneResolutionUnit (0xFFFFFFFF)
	
	,	fCFARepeatPatternRows (0)
	,	fCFARepeatPatternCols (0)
	
	,	fImageUniqueID ()
	
	,	fGPSVersionID		  (0)
	,	fGPSLatitudeRef		  ()
	,	fGPSLongitudeRef	  ()
	,	fGPSAltitudeRef		  (0xFFFFFFFF)
	,	fGPSAltitude		  ()
	,	fGPSSatellites		  ()
	,	fGPSStatus			  ()
	,	fGPSMeasureMode		  ()
	,	fGPSDOP				  ()
	,	fGPSSpeedRef		  ()
	,	fGPSSpeed			  ()
	,	fGPSTrackRef		  ()
	,	fGPSTrack			  ()
	,	fGPSImgDirectionRef	  ()
	,	fGPSImgDirection	  ()
	,	fGPSMapDatum		  ()
	,	fGPSDestLatitudeRef	  ()
	,	fGPSDestLongitudeRef  ()
	,	fGPSDestBearingRef	  ()
	,	fGPSDestBearing		  ()
	,	fGPSDestDistanceRef	  ()
	,	fGPSDestDistance	  ()
	,	fGPSProcessingMethod  ()
	,	fGPSAreaInformation	  ()
	,	fGPSDateStamp		  ()
	,	fGPSDifferential	  (0xFFFFFFFF)
	,	fGPSHPositioningError ()
	
	,	fInteroperabilityIndex ()

	,	fInteroperabilityVersion (0)
	
	,	fRelatedImageFileFormat ()
	
	,	fRelatedImageWidth  (0)
	,	fRelatedImageLength (0)
	
	,	fCameraSerialNumber ()
	
	,	fLensID			  ()
	,	fLensMake		  ()
	,	fLensName		  ()
	,	fLensSerialNumber ()
	
	,	fLensNameWasReadFromExif (false)

	,	fApproxFocusDistance ()

	,	fFlashCompensation ()
	
	,	fOwnerName ()
	,	fFirmware  ()
	
	{
	
	uint32 j;
	uint32 k;
	
	fISOSpeedRatings [0] = 0;
	fISOSpeedRatings [1] = 0;
	fISOSpeedRatings [2] = 0;
	
	for (j = 0; j < kMaxCFAPattern; j++)
		for (k = 0; k < kMaxCFAPattern; k++)
			{
			fCFAPattern [j] [k] = 255;
			}
		
	}
	
/*****************************************************************************/

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

dng_exif * dng_exif::Clone () const
	{
	
	dng_exif *result = new dng_exif (*this);
	
	if (!result)
		{
		ThrowMemoryFull ();
		}
	
	return result;
	
	}
		
/*****************************************************************************/

void dng_exif::SetEmpty ()
	{
	
	*this = dng_exif ();
	
	}
		
/*****************************************************************************/

void dng_exif::CopyGPSFrom (const dng_exif &exif)
	{
			
	fGPSVersionID         = exif.fGPSVersionID;
	fGPSLatitudeRef       = exif.fGPSLatitudeRef;
	fGPSLatitude [0]	  = exif.fGPSLatitude [0];
	fGPSLatitude [1]	  = exif.fGPSLatitude [1];
	fGPSLatitude [2]	  = exif.fGPSLatitude [2];
	fGPSLongitudeRef      = exif.fGPSLongitudeRef;
	fGPSLongitude [0]	  = exif.fGPSLongitude [0];
	fGPSLongitude [1]	  = exif.fGPSLongitude [1];
	fGPSLongitude [2]	  = exif.fGPSLongitude [2];
	fGPSAltitudeRef       = exif.fGPSAltitudeRef;
	fGPSAltitude          = exif.fGPSAltitude;
	fGPSTimeStamp [0]     = exif.fGPSTimeStamp [0];
	fGPSTimeStamp [1]     = exif.fGPSTimeStamp [1];
	fGPSTimeStamp [2]     = exif.fGPSTimeStamp [2];
	fGPSSatellites        = exif.fGPSSatellites;
	fGPSStatus            = exif.fGPSStatus;
	fGPSMeasureMode       = exif.fGPSMeasureMode;
	fGPSDOP               = exif.fGPSDOP;
	fGPSSpeedRef          = exif.fGPSSpeedRef;
	fGPSSpeed             = exif.fGPSSpeed;
	fGPSTrackRef          = exif.fGPSTrackRef;
	fGPSTrack             = exif.fGPSTrack;
	fGPSImgDirectionRef   = exif.fGPSImgDirectionRef;
	fGPSImgDirection      = exif.fGPSImgDirection;
	fGPSMapDatum          = exif.fGPSMapDatum;
	fGPSDestLatitudeRef   = exif.fGPSDestLatitudeRef;
	fGPSDestLatitude [0]  = exif.fGPSDestLatitude [0];
	fGPSDestLatitude [1]  = exif.fGPSDestLatitude [1];
	fGPSDestLatitude [2]  = exif.fGPSDestLatitude [2];
	fGPSDestLongitudeRef  = exif.fGPSDestLongitudeRef;
	fGPSDestLongitude [0] = exif.fGPSDestLongitude [0];
	fGPSDestLongitude [1] = exif.fGPSDestLongitude [1];
	fGPSDestLongitude [2] = exif.fGPSDestLongitude [2];
	fGPSDestBearingRef    = exif.fGPSDestBearingRef;
	fGPSDestBearing       = exif.fGPSDestBearing;
	fGPSDestDistanceRef   = exif.fGPSDestDistanceRef;
	fGPSDestDistance      = exif.fGPSDestDistance;
	fGPSProcessingMethod  = exif.fGPSProcessingMethod;
	fGPSAreaInformation   = exif.fGPSAreaInformation;
	fGPSDateStamp         = exif.fGPSDateStamp;
	fGPSDifferential      = exif.fGPSDifferential;
	fGPSHPositioningError = exif.fGPSHPositioningError;

	}

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

// Fix up common errors and rounding issues with EXIF exposure times.
	
real64 dng_exif::SnapExposureTime (real64 et)
	{
	
	// Protection against invalid values.
	
	if (et <= 0.0)
		return 0.0;
	
	// If near a standard shutter speed, snap to it.
	
	static const real64 kStandardSpeed [] =
		{
		30.0,
		25.0,
		20.0,
		15.0,
		13.0,
		10.0,
		8.0,
		6.0,
		5.0,
		4.0,
		3.2,
		3.0,
		2.5,
		2.0,
		1.6,
		1.5,
		1.3,
		1.0,
		0.8,
		0.7,
		0.6,
		0.5,
		0.4,
		0.3,
		1.0 / 4.0,
		1.0 / 5.0,
		1.0 / 6.0,
		1.0 / 8.0,
		1.0 / 10.0,
		1.0 / 13.0,
		1.0 / 15.0,
		1.0 / 20.0,
		1.0 / 25.0,
		1.0 / 30.0,
		1.0 / 40.0,
		1.0 / 45.0,
		1.0 / 50.0,
		1.0 / 60.0,
		1.0 / 80.0,
		1.0 / 90.0,
		1.0 / 100.0,
		1.0 / 125.0,
		1.0 / 160.0,
		1.0 / 180.0,
		1.0 / 200.0,
		1.0 / 250.0,
		1.0 / 320.0,
		1.0 / 350.0,
		1.0 / 400.0,
		1.0 / 500.0,
		1.0 / 640.0,
		1.0 / 750.0,
		1.0 / 800.0,
		1.0 / 1000.0,
		1.0 / 1250.0,
		1.0 / 1500.0,
		1.0 / 1600.0,
		1.0 / 2000.0,
		1.0 / 2500.0,
		1.0 / 3000.0,
		1.0 / 3200.0,
		1.0 / 4000.0,
		1.0 / 5000.0,
		1.0 / 6000.0,
		1.0 / 6400.0,
		1.0 / 8000.0,
		1.0 / 10000.0,
		1.0 / 12000.0,
		1.0 / 12800.0,
		1.0 / 16000.0
		};
		
	uint32 count = sizeof (kStandardSpeed    ) /
				   sizeof (kStandardSpeed [0]);
					   
	for (uint32 fudge = 0; fudge <= 1; fudge++)
		{
		
		real64 testSpeed = et;
		
		if (fudge == 1)
			{
			
			// Often APEX values are rounded to a power of two,
			// which results in non-standard shutter speeds.
			
			if (et >= 0.1)
				{
				
				// No fudging slower than 1/10 second
				
				break;
				
				}
			
			else if (et >= 0.01)
				{
				
				// Between 1/10 and 1/100 the commonly misrounded
				// speeds are 1/15, 1/30, 1/60, which are often encoded as
				// 1/16, 1/32, 1/64.  Try fudging and see if we get
				// near a standard speed.
				
				testSpeed *= 16.0 / 15.0;
				
				}
				
			else
				{
				
				// Faster than 1/100, the commonly misrounded
				// speeds are 1/125, 1/250, 1/500, etc., which
				// are often encoded as 1/128, 1/256, 1/512.
				
				testSpeed *= 128.0 / 125.0;
				
				}
			
			}
			
		for (uint32 index = 0; index < count; index++)
			{
			
			if (testSpeed >= kStandardSpeed [index] * 0.98 &&
				testSpeed <= kStandardSpeed [index] * 1.02)
				{
				
				return kStandardSpeed [index];
				
				}
				
			}
			
		}
		
	// We are not near any standard speeds.  Round the non-standard value to something
	// that looks reasonable.
	
	if (et >= 10.0)
		{
		
		// Round to nearest second.
		
		et = floor (et + 0.5);
		
		}
		
	else if (et >= 0.5)
		{
		
		// Round to nearest 1/10 second
		
		et = floor (et * 10.0 + 0.5) * 0.1;
		
		}
		
	else if (et >= 1.0 / 20.0)
		{
		
		// Round to an exact inverse.
		
		et = 1.0 / floor (1.0 / et + 0.5);
		
		}
		
	else if (et >= 1.0 / 130.0)
		{
		
		// Round inverse to multiple of 5
		
		et = 0.2 / floor (0.2 / et + 0.5);
		
		}
		
	else if (et >= 1.0 / 750.0)
		{
		
		// Round inverse to multiple of 10
		
		et = 0.1 / floor (0.1 / et + 0.5);
		
		}
		
	else if (et >= 1.0 / 1300.0)
		{
		
		// Round inverse to multiple of 50
		
		et = 0.02 / floor (0.02 / et + 0.5);
		
		}
		
	else if (et >= 1.0 / 15000.0)
		{
		
		// Round inverse to multiple of 100
		
		et = 0.01 / floor (0.01 / et + 0.5);
		
		}
		
	else
		{
		
		// Round inverse to multiple of 1000
		
		et = 0.001 / floor (0.001 / et + 0.5);
		
		}
		
	return et;
	
	}

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

void dng_exif::SetExposureTime (real64 et, bool snap)
	{
	
	fExposureTime.Clear ();
	
	fShutterSpeedValue.Clear ();
	
	if (snap)
		{
		
		et = SnapExposureTime (et);
		
		}
		
	if (et >= 1.0 / 32768.0 && et <= 32768.0)
		{
		
		if (et >= 100.0)
			{
			
			fExposureTime.Set_real64 (et, 1);
			
			}
			
		else if (et >= 1.0)
			{
			
			fExposureTime.Set_real64 (et, 10);
			
			fExposureTime.ReduceByFactor (10);
			
			}
			
		else if (et <= 0.1)
			{
			
			fExposureTime = dng_urational (1, Round_uint32 (1.0 / et));
			
			}
			
		else
			{
			
			fExposureTime.Set_real64 (et, 100);
			
			fExposureTime.ReduceByFactor (10);
				
			for (uint32 f = 2; f <= 9; f++)
				{
				
				real64 z = 1.0 / (real64) f / et;
				
				if (z >= 0.99 && z <= 1.01)
					{
					
					fExposureTime = dng_urational (1, f);
					
					break;
					
					}
				
				}
					
			}
		
		// Now mirror this value to the ShutterSpeedValue field.
		
		et = fExposureTime.As_real64 ();
		
		fShutterSpeedValue.Set_real64 (-log (et) / log (2.0), 1000000);
												
		fShutterSpeedValue.ReduceByFactor (10);									
		fShutterSpeedValue.ReduceByFactor (10);									
		fShutterSpeedValue.ReduceByFactor (10);									
		fShutterSpeedValue.ReduceByFactor (10);									
		fShutterSpeedValue.ReduceByFactor (10);									
		fShutterSpeedValue.ReduceByFactor (10);									

		}
		
	}

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

void dng_exif::SetShutterSpeedValue (real64 ss)
	{
	
	if (fExposureTime.NotValid ())
		{
		
		real64 et = pow (2.0, -ss);
		
		SetExposureTime (et, true);
		
		}
	
	}

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

dng_urational dng_exif::EncodeFNumber (real64 fs)
	{
	
	dng_urational y;

	if (fs > 10.0)
		{
		
		y.Set_real64 (fs, 1);
		
		}
		
	else if (fs < 1.0)
		{
		
		y.Set_real64 (fs, 100);
		
		y.ReduceByFactor (10);
		y.ReduceByFactor (10);
		
		}
		
	else
		{
		
		y.Set_real64 (fs, 10);
		
		y.ReduceByFactor (10);
		
		}
		
	return y;
			
	}
		
/*****************************************************************************/

void dng_exif::SetFNumber (real64 fs)
	{
	
	fFNumber.Clear ();
	
	fApertureValue.Clear ();

	// Allow f-number values less than 1.0 (e.g., f/0.95), even though they would
	// correspond to negative APEX values, which the EXIF specification does not
	// support (ApertureValue is a rational, not srational). The ApertureValue tag
	// will be omitted in the case where fs < 1.0.
	
	if (fs > 0.0 && fs <= 32768.0)
		{
	
		fFNumber = EncodeFNumber (fs);
		
		// Now mirror this value to the ApertureValue field.
		
		real64 av = FNumberToApertureValue (fFNumber);

		if (av >= 0.0 && av <= 99.99)
			{
			
			fApertureValue.Set_real64 (av, 1000000);
			
			fApertureValue.ReduceByFactor (10);									
			fApertureValue.ReduceByFactor (10);									
			fApertureValue.ReduceByFactor (10);									
			fApertureValue.ReduceByFactor (10);									
			fApertureValue.ReduceByFactor (10);									
			fApertureValue.ReduceByFactor (10);									
			
			}
		
		}
	
	}
			
/*****************************************************************************/

void dng_exif::SetApertureValue (real64 av)
	{

	if (fFNumber.NotValid ())
		{
		
		SetFNumber (ApertureValueToFNumber (av));
						
		}
		
	}

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

real64 dng_exif::ApertureValueToFNumber (real64 av)
	{
	
	return pow (2.0, 0.5 * av);
	
	}

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

real64 dng_exif::ApertureValueToFNumber (const dng_urational &av)
	{
	
	return ApertureValueToFNumber (av.As_real64 ());
	
	}

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

real64 dng_exif::FNumberToApertureValue (real64 fNumber)
	{
	
	return 2.0 * log (fNumber) / log (2.0);
	
	}

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

real64 dng_exif::FNumberToApertureValue (const dng_urational &fNumber)
	{
	
	return FNumberToApertureValue (fNumber.As_real64 ());
	
	}
			
/*****************************************************************************/

void dng_exif::UpdateDateTime (const dng_date_time_info &dt)
	{
	
	fDateTime = dt;
	
	}

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

bool dng_exif::AtLeastVersion0230 () const
	{
	
	uint32 b0 = (fExifVersion >> 24) & 0xff;
	uint32 b1 = (fExifVersion >> 16) & 0xff;
	uint32 b2 = (fExifVersion >>  8) & 0xff;

	return (b0 > 0) || (b1 >= 2 && b2 >= 3);
	
	}
		
/*****************************************************************************/

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

		}
		
	if (parentCode == 0 || isMainIFD)
		{
		
		if (Parse_ifd0_main (stream,
		 				     shared,
						 	 parentCode,
						 	 tagCode,
						 	 tagType,
						 	 tagCount,
						 	 tagOffset))
			{
			
			return true;
			
			}

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

		}
		
	if (parentCode == tcGPSInfo)
		{
		
		if (Parse_gps (stream,
		 			   shared,
					   parentCode,
					   tagCode,
					   tagType,
					   tagCount,
					   tagOffset))
			{
			
			return true;
			
			}

		}
		
	if (parentCode == tcInteroperabilityIFD)
		{
		
		if (Parse_interoperability (stream,
		 			   				shared,
									parentCode,
									tagCode,
									tagType,
									tagCount,
									tagOffset))
			{
			
			return true;
			
			}

		}
		
	return false;
		
	}

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

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

bool dng_exif::Parse_ifd0 (dng_stream &stream,
		 			   	   dng_shared & /* shared */,
						   uint32 parentCode,
						   uint32 tagCode,
						   uint32 tagType,
						   uint32 tagCount,
						   uint64 /* tagOffset */)
	{
	
	switch (tagCode)
		{
			
		case tcImageDescription:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fImageDescription);
							
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("ImageDescription: ");
				
				DumpString (fImageDescription);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcMake:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fMake);
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("Make: ");
				
				DumpString (fMake);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
				
		case tcModel:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fModel);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("Model: ");
				
				DumpString (fModel);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
				
		case tcSoftware:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fSoftware);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("Software: ");
				
				DumpString (fSoftware);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcDateTime:
			{
			
			uint64 tagPosition = stream.PositionInOriginalFile ();
			
			dng_date_time dt;
				
			if (!ParseDateTimeTag (stream,
								   parentCode,
								   tagCode,
								   tagType,
								   tagCount,
								   dt))
				{
				return false;
				}
				
			fDateTime.SetDateTime (dt);
				
			fDateTimeStorageInfo = dng_date_time_storage_info (tagPosition,
															   dng_date_time_format_exif);
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("DateTime: ");
				
				DumpDateTime (fDateTime.DateTime ());
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}

		case tcArtist:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fArtist);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("Artist: ");
				
				DumpString (fArtist);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcCopyright:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseDualStringTag (stream,
								parentCode,
								tagCode,
								tagCount,
								fCopyright,
								fCopyright2);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("Copyright: ");
				
				DumpString (fCopyright);
				
				if (fCopyright2.Get () [0] != 0)
					{
					
					printf (" ");
					
					DumpString (fCopyright2);
					
					}
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcTIFF_EP_StandardID:
			{
			
			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 ();
			
			fTIFF_EP_StandardID = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("TIFF/EPStandardID: %u.%u.%u.%u\n",
						(unsigned) b0,
						(unsigned) b1, 
						(unsigned) b2,
						(unsigned) b3);
				}
				
			#endif
			
			break;
			
			}
				
		case tcCameraSerialNumber:
		case tcKodakCameraSerialNumber:		// Kodak uses a very similar tag.
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fCameraSerialNumber);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (fCameraSerialNumber);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcLensInfo:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 4))
				return false;
				
			fLensInfo [0] = stream.TagValue_urational (tagType);
			fLensInfo [1] = stream.TagValue_urational (tagType);
			fLensInfo [2] = stream.TagValue_urational (tagType);
			fLensInfo [3] = stream.TagValue_urational (tagType);
			
			// Some third party software wrote zero rather and undefined values
			// for unknown entries.  Work around this bug.
			
			for (uint32 j = 0; j < 4; j++)
				{
			
				if (fLensInfo [j].IsValid () && fLensInfo [j].As_real64 () <= 0.0)
					{
					
					fLensInfo [j] = dng_urational (0, 0);
					
					#if qDNGValidate
					
					ReportWarning ("Zero entry in LensInfo tag--should be undefined");
					
					#endif

					}
					
				}
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("LensInfo: ");
				
				real64 minFL = fLensInfo [0].As_real64 ();
				real64 maxFL = fLensInfo [1].As_real64 ();
				
				if (minFL == maxFL)
					printf ("%0.1f mm", minFL);
				else
					printf ("%0.1f-%0.1f mm", minFL, maxFL);
					
				if (fLensInfo [2].d)
					{
					
					real64 minFS = fLensInfo [2].As_real64 ();
					real64 maxFS = fLensInfo [3].As_real64 ();
					
					if (minFS == maxFS)
						printf (" f/%0.1f", minFS);
					else
						printf (" f/%0.1f-%0.1f", minFS, maxFS);
					
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
				
		default:
			{
			
			return false;
			
			}
			
		}
	
	return true;
	
	}
	
/*****************************************************************************/

// Parses tags that should only appear in IFD 0 or the main image IFD.

bool dng_exif::Parse_ifd0_main (dng_stream &stream,
		 			   	        dng_shared & /* shared */,
						  	    uint32 parentCode,
						  	    uint32 tagCode,
						  	    uint32 tagType,
						  	    uint32 tagCount,
						  	    uint64 /* tagOffset */)
	{
	
	switch (tagCode)
		{
			
		case tcFocalPlaneXResolution:
			{

			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneXResolution = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneXResolution: %0.4f\n",
						fFocalPlaneXResolution.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcFocalPlaneYResolution:
			{

			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneYResolution = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneYResolution: %0.4f\n",
						fFocalPlaneYResolution.As_real64 ());
						
				}
				
			#endif
			
			break;
			
			}
			
		case tcFocalPlaneResolutionUnit:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneResolutionUnit = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneResolutionUnit: %s\n",
					    LookupResolutionUnit (fFocalPlaneResolutionUnit));
				
				}
				
			#endif
				
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("SensingMethod: %s\n",
						LookupSensingMethod (fSensingMethod));

				}
				
			#endif
			
			break;
			
			}
			
		default:
			{
			
			return false;
			
			}
			
		}
		
	return true;
	
	}
			
/*****************************************************************************/

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

bool dng_exif::Parse_ifd0_exif (dng_stream &stream,
								dng_shared & /* shared */,
						  	   	uint32 parentCode,
						  	    uint32 tagCode,
						  	    uint32 tagType,
						  	    uint32 tagCount,
						  	    uint64 /* tagOffset */)
	{
	
	switch (tagCode)
		{
		
		case tcBatteryLevel:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational, ttAscii);
			
			if (tagType == ttAscii)
				{
				
				ParseStringTag (stream,
								parentCode,
								tagCode,
								tagCount,
								fBatteryLevelA);
			
				}
				
			else
				{
			
				CheckTagCount (parentCode, tagCode, tagCount, 1);
				
				fBatteryLevelR = stream.TagValue_urational (tagType);
				
				}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("BatteryLevel: ");
				
				if (tagType == ttAscii)
					{
					
					DumpString (fBatteryLevelA);
					
					}
					
				else
					{
				
					printf ("%0.2f", fBatteryLevelR.As_real64 ());
					
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
		
		case tcExposureTime:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			dng_urational et = stream.TagValue_urational (tagType);
			
			SetExposureTime (et.As_real64 (), true);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ExposureTime: ");
				
				DumpExposureTime (et.As_real64 ());
									
				printf ("\n");

				}
				
			if (et.As_real64 () <= 0.0)
				{
				
				ReportWarning ("The ExposureTime is <= 0");
							 
				}
			
			#endif
			
			break;
			
			}

		case tcFNumber:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			dng_urational fs = stream.TagValue_urational (tagType);
			
			// Sometimes "unknown" is recorded as zero.
			
			if (fs.As_real64 () <= 0.0)
				{
				fs.Clear ();
				}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FNumber: f/%0.2f\n",
						fs.As_real64 ());
				
				}
				
			#endif
			
			SetFNumber (fs.As_real64 ());
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("ExposureProgram: %s\n",
						LookupExposureProgram (fExposureProgram));
				
				}
				
			#endif
			
			break;
			
			}

		case tcISOSpeedRatings:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1, 3);
			
			for (uint32 j = 0; j < tagCount && j < 3; j++)
				{
				
				fISOSpeedRatings [j] = stream.TagValue_uint32 (tagType);
				
				}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ISOSpeedRatings:");
				
				for (uint32 j = 0; j < tagCount && j < 3; j++)
					{
					
					printf (" %u", (unsigned) fISOSpeedRatings [j]);
					
					}
					
				printf ("\n");
					
				}
				
			#endif
			
			break;
			
			}
			
		case tcSensitivityType:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fSensitivityType = (uint32) stream.Get_uint16 ();
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SensitivityType: %s\n",
						LookupSensitivityType (fSensitivityType));
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcStandardOutputSensitivity:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fStandardOutputSensitivity = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("StandardOutputSensitivity: %u\n",
						(unsigned) fStandardOutputSensitivity);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcRecommendedExposureIndex:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fRecommendedExposureIndex = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("RecommendedExposureIndex: %u\n",
						(unsigned) fRecommendedExposureIndex);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcISOSpeed:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fISOSpeed = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ISOSpeed: %u\n",
						(unsigned) fISOSpeed);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcISOSpeedLatitudeyyy:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fISOSpeedLatitudeyyy = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ISOSpeedLatitudeyyy: %u\n",
						(unsigned) fISOSpeedLatitudeyyy);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcISOSpeedLatitudezzz:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);

			fISOSpeedLatitudezzz = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ISOSpeedLatitudezzz: %u\n",
						(unsigned) fISOSpeedLatitudezzz);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcTimeZoneOffset:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1, 2);
			
			dng_time_zone zoneOriginal;
			
			zoneOriginal.SetOffsetHours (stream.TagValue_int32 (tagType));
			
			fDateTimeOriginal.SetZone (zoneOriginal);
			
			#if 0	// MWG: Not filling in time zones unless we are sure.
			
			// Note that there is no "TimeZoneOffsetDigitized" field, so
			// we assume the same tone zone as the original.
			
			fDateTimeDigitized.SetZone (zoneOriginal);
			
			#endif

			dng_time_zone zoneCurrent;

			if (tagCount >= 2)
				{
				
				zoneCurrent.SetOffsetHours (stream.TagValue_int32 (tagType));
				
				fDateTime.SetZone (zoneCurrent);
				
				}
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("TimeZoneOffset: DateTimeOriginal = %d", 
						(int) zoneOriginal.ExactHourOffset ());
						
				if (tagCount >= 2)
					{
				
					printf (", DateTime = %d",
							(int) zoneCurrent.ExactHourOffset ());
							
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("SelfTimerMode: ");
				
				if (fSelfTimerMode)
					{
					
					printf ("%u sec", (unsigned) fSelfTimerMode);
					
					}
					
				else
					{
					
					printf ("Off");
					
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcExifVersion:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			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 ();
			
			fExifVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				real64 x = (b0 - '0') * 10.00 +
						   (b1 - '0') *  1.00 +
						   (b2 - '0') *  0.10 +
						   (b3 - '0') *  0.01;
						   
				printf ("ExifVersion: %0.2f\n", x);
				
				}
				
			#endif
			
			break;
			
			}

		case tcDateTimeOriginal:
			{
			
			uint64 tagPosition = stream.PositionInOriginalFile ();
			
			dng_date_time dt;
			
			if (!ParseDateTimeTag (stream,
								   parentCode,
								   tagCode,
								   tagType,
								   tagCount,
								   dt))
				{
				return false;
				}
				
			fDateTimeOriginal.SetDateTime (dt);
			
			fDateTimeOriginalStorageInfo = dng_date_time_storage_info (tagPosition,
																	   dng_date_time_format_exif);
				
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("DateTimeOriginal: ");
				
				DumpDateTime (fDateTimeOriginal.DateTime ());
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}

		case tcDateTimeDigitized:
			{
			
			uint64 tagPosition = stream.PositionInOriginalFile ();
			
			dng_date_time dt;
			
			if (!ParseDateTimeTag (stream,
								   parentCode,
								   tagCode,
								   tagType,
								   tagCount,
								   dt))
				{
				return false;
				}
				
			fDateTimeDigitized.SetDateTime (dt);
			
			fDateTimeDigitizedStorageInfo = dng_date_time_storage_info (tagPosition,
																	    dng_date_time_format_exif);

			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("DateTimeDigitized: ");
				
				DumpDateTime (fDateTimeDigitized.DateTime ());
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}
			
		case tcComponentsConfiguration:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			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 ();
			
			fComponentsConfiguration = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ComponentsConfiguration: %s %s %s %s\n",
						LookupComponent (b0),
						LookupComponent (b1),
						LookupComponent (b2),
						LookupComponent (b3));
								
				}
				
			#endif
			
			break;
			
			}
			
		case tcCompressedBitsPerPixel:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCompresssedBitsPerPixel = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CompressedBitsPerPixel: %0.2f\n",
						fCompresssedBitsPerPixel.As_real64 ());
								
				}
				
			#endif
			
			break;
			
			}
			
		case tcShutterSpeedValue:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			dng_srational ss = stream.TagValue_srational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ShutterSpeedValue: ");
				
				real64 x = pow (2.0, -ss.As_real64 ());
				
				DumpExposureTime (x);
														
				printf ("\n");

				}
				
			// The ExposureTime and ShutterSpeedValue tags should be consistent.
			
			if (fExposureTime.IsValid ())
				{
				
				real64 et = fExposureTime.As_real64 ();
				
				real64 tv1 = -1.0 * log (et) / log (2.0);
				
				real64 tv2 = ss.As_real64 ();
				
				// Make sure they are within 0.25 APEX values.
				
				if (Abs_real64 (tv1 - tv2) > 0.25)
					{
					
					ReportWarning ("The ExposureTime and ShutterSpeedValue tags have conflicting values");
								 
					}
					
				}
			
			#endif
			
			SetShutterSpeedValue (ss.As_real64 ());
				
			break;
			
			}
			
		case tcApertureValue:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			dng_urational av = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				real64 x = pow (2.0, 0.5 * av.As_real64 ());
								
				printf ("ApertureValue: f/%0.2f\n", x);
				
				}
				
			// The FNumber and ApertureValue tags should be consistent.
			
			if (fFNumber.IsValid () && av.IsValid ())
				{
				
				real64 fs = fFNumber.As_real64 ();
				
				real64 av1 = FNumberToApertureValue (fs);
			
				real64 av2 = av.As_real64 ();
			
				if (Abs_real64 (av1 - av2) > 0.25)
					{
					
					ReportWarning ("The FNumber and ApertureValue tags have conflicting values");
								 
					}

				}
		
			#endif
			
			SetApertureValue (av.As_real64 ());
				
			break;
			
			}
			
		case tcBrightnessValue:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fBrightnessValue = stream.TagValue_srational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("BrightnessValue: %0.2f\n",
						fBrightnessValue.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcExposureBiasValue:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttSRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fExposureBiasValue = stream.TagValue_srational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ExposureBiasValue: %0.2f\n",
						fExposureBiasValue.As_real64 ());
				
				}
				
			#endif
			
			break;
			
			}

		case tcMaxApertureValue:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fMaxApertureValue = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				real64 x = pow (2.0, 0.5 * fMaxApertureValue.As_real64 ());
								
				printf ("MaxApertureValue: f/%0.1f\n", x);
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcSubjectDistance:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSubjectDistance = stream.TagValue_urational (tagType);

			fApproxFocusDistance = fSubjectDistance;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("SubjectDistance: %u/%u\n",
						(unsigned) fSubjectDistance.n,
						(unsigned) fSubjectDistance.d);
				
				}
			
			#endif
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("MeteringMode: %s\n",
						LookupMeteringMode (fMeteringMode));
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcLightSource:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fLightSource = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

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

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

			if (gVerbose)
				{
				
				printf ("Flash: %u\n", (unsigned) fFlash);
				
				if ((fFlash >> 5) & 1)
					{
					printf ("    No flash function\n");
					}
					
				else
					{
					
					if (fFlash & 0x1)
						{
						
						printf ("    Flash fired\n");
						
						switch ((fFlash >> 1) & 0x3)
							{
							
							case 2:
								printf ("    Strobe return light not detected\n");
								break;
								
							case 3:
								printf ("    Strobe return light detected\n");
								break;
								
							}
								
						}
						
					else
						{
						printf ("    Flash did not fire\n");
						}
						
					switch ((fFlash >> 3) & 0x3)
						{
						
						case 1:
							printf ("    Compulsory flash firing\n");
							break;
							
						case 2:
							printf ("    Compulsory flash suppression\n");
							break;
							
						case 3:
							printf ("    Auto mode\n");
							break;
							
						}
						
					if ((fFlash >> 6) & 1)
						{
						printf ("    Red-eye reduction supported\n");
						}

					}
					
				}
				
			#endif
			
			break;
			
			}
			
		case tcFocalLength:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalLength = stream.TagValue_urational (tagType);
			
			// Sometimes "unknown" is recorded as zero.
			
			if (fFocalLength.As_real64 () <= 0.0)
				{
				fFocalLength.Clear ();
				}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalLength: %0.1f mm\n",
						fFocalLength.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcImageNumber:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fImageNumber = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				printf ("ImageNumber: %u\n", (unsigned) fImageNumber);
				}
				
			#endif
				
			break;
			
			}
		
		case tcExposureIndex:
		case tcExposureIndexExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fExposureIndex = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ISO %0.1f\n",
						LookupTagCode (parentCode, tagCode),
						fExposureIndex.As_real64 ());
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcUserComment:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			ParseEncodedStringTag (stream,
								   parentCode,
								   tagCode,
				    			   tagCount,
				    			   fUserComment);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("UserComment: ");
				
				DumpString (fUserComment);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}

		case tcSubsecTime:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			dng_string subsecs;
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							subsecs);
							
			fDateTime.SetSubseconds (subsecs);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SubsecTime: ");
				
				DumpString (subsecs);
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}

		case tcSubsecTimeOriginal:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			dng_string subsecs;
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							subsecs);
							
			fDateTimeOriginal.SetSubseconds (subsecs);

			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SubsecTimeOriginal: ");
				
				DumpString (subsecs);
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}

		case tcSubsecTimeDigitized:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			dng_string subsecs;
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							subsecs);
							
			fDateTimeDigitized.SetSubseconds (subsecs);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SubsecTimeDigitized: ");
				
				DumpString (subsecs);
				
				printf ("\n");
				
				}
				
			#endif

			break;
			
			}
			
		case tcFlashPixVersion:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			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 ();
			
			fFlashPixVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				real64 x = (b0 - '0') * 10.00 +
						   (b1 - '0') *  1.00 +
						   (b2 - '0') *  0.10 +
						   (b3 - '0') *  0.01;
						   
				printf ("FlashPixVersion: %0.2f\n", x);
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcColorSpace:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fColorSpace = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ColorSpace: %s\n",
						LookupColorSpace (fColorSpace));
				
				}
				
			#endif
			
			break;
			
			}
			
		case tcPixelXDimension:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPixelXDimension = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				printf ("PixelXDimension: %u\n", (unsigned) fPixelXDimension);
				}
				
			#endif
				
			break;
			
			}
			
		case tcPixelYDimension:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fPixelYDimension = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				printf ("PixelYDimension: %u\n", (unsigned) fPixelYDimension);
				}
				
			#endif
			
			break;
			
			}

		case tcFocalPlaneXResolutionExif:
			{

			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneXResolution = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneXResolutionExif: %0.4f\n",
						fFocalPlaneXResolution.As_real64 ());
						
				}
			
			#endif
			
			break;
			
			}
			
		case tcFocalPlaneYResolutionExif:
			{

			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneYResolution = stream.TagValue_urational (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneYResolutionExif: %0.4f\n",
						fFocalPlaneYResolution.As_real64 ());
						
				}
			
			#endif
			
			break;
			
			}
			
		case tcFocalPlaneResolutionUnitExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFocalPlaneResolutionUnit = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FocalPlaneResolutionUnitExif: %s\n",
					    LookupResolutionUnit (fFocalPlaneResolutionUnit));
				
				}
				
			#endif
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("SensingMethodExif: %s\n",
						LookupSensingMethod (fSensingMethod));

				}
			
			#endif
			
			break;
			
			}
			
		case tcFileSource:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fFileSource = stream.Get_uint8 ();
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("FileSource: %s\n",
						LookupFileSource (fFileSource));

				}
			
			#endif
			
			break;
			
			}
			
		case tcSceneType:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSceneType = stream.Get_uint8 ();
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SceneType: %s\n",
						LookupSceneType (fSceneType));

				}
			
			#endif
			
			break;
			
			}
			
		case tcCFAPatternExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			if (tagCount <= 4)
				{
				return false;
				}
				
			uint32 cols = stream.Get_uint16 ();
			uint32 rows = stream.Get_uint16 ();
			
			if (tagCount != 4 + cols * rows)
				{
				return false;
				}
				
			if (cols < 1 || cols > kMaxCFAPattern ||
				rows < 1 || rows > kMaxCFAPattern)
				{
				return false;
				}
			
			fCFARepeatPatternCols = cols;
			fCFARepeatPatternRows = rows;
			
			// Note that the Exif spec stores this array in a different
			// scan order than the TIFF-EP spec.
			
			for (uint32 j = 0; j < fCFARepeatPatternCols; j++)
				for (uint32 k = 0; k < fCFARepeatPatternRows; k++)
					{
					
					fCFAPattern [k] [j] = stream.Get_uint8 ();
					
					}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CFAPatternExif:\n");
				
				for (uint32 j = 0; j < fCFARepeatPatternRows; j++)
					{
					
					int32 spaces = 4;
					
					for (uint32 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 tcCustomRendered:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fCustomRendered = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("CustomRendered: %s\n",
						LookupCustomRendered (fCustomRendered));

				}
			
			#endif
			
			break;
			
			}
			
		case tcExposureMode:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fExposureMode = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("ExposureMode: %s\n",
						LookupExposureMode (fExposureMode));

				}
			
			#endif
			
			break;
			
			}
			
		case tcWhiteBalance:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fWhiteBalance = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("WhiteBalance: %s\n",
						LookupWhiteBalance (fWhiteBalance));

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

			if (gVerbose)
				{
				
				printf ("DigitalZoomRatio: ");
				
				if (fDigitalZoomRatio.n == 0 ||
					fDigitalZoomRatio.d == 0)
					{
					
					printf ("Not used\n");
					
					}
					
				else
					{
				
					printf ("%0.2f\n", fDigitalZoomRatio.As_real64 ());
					
					}
	
				}
			
			#endif
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("FocalLengthIn35mmFilm: %u mm\n",
						(unsigned) fFocalLengthIn35mmFilm);
					
				}
			
			#endif
			
			break;
			
			}

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

			if (gVerbose)
				{
				
				printf ("SceneCaptureType: %s\n",
						LookupSceneCaptureType (fSceneCaptureType));

				}
			
			#endif
			
			break;
			
			}
			
		case tcGainControl:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fGainControl = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("GainControl: %s\n",
						LookupGainControl (fGainControl));

				}
			
			#endif
			
			break;
			
			}
			
		case tcContrast:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fContrast = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("Contrast: %s\n",
						LookupContrast (fContrast));

				}
			
			#endif
			
			break;
			
			}
			
		case tcSaturation:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSaturation = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("Saturation: %s\n",
						LookupSaturation (fSaturation));

				}
			
			#endif
			
			break;
			
			}
			
		case tcSharpness:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSharpness = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("Sharpness: %s\n",
						LookupSharpness (fSharpness));

				}
			
			#endif
			
			break;
			
			}
			
		case tcSubjectDistanceRange:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fSubjectDistanceRange = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("SubjectDistanceRange: %s\n",
						LookupSubjectDistanceRange (fSubjectDistanceRange));

				}
				
			#endif
			
			break;
			
			}
			
		case tcSubjectArea:
		case tcSubjectLocation:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 2, 4))
				{
				return false;
				}
				
			if (tagCode == tcSubjectLocation)
				{
				CheckTagCount (parentCode, tagCode, tagCount, 2);
				}
				
			fSubjectAreaCount = tagCount;
			
			for (uint32 j = 0; j < tagCount; j++)
				{
				
				fSubjectArea [j] = stream.TagValue_uint32 (tagType);
				
				}
			
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s:", LookupTagCode (parentCode, tagCode));
				
				for (uint32 j = 0; j < fSubjectAreaCount; j++)
					{
					
					printf (" %u", (unsigned) fSubjectArea [j]);
					
					}
					
				printf ("\n");

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

			if (gVerbose)
				{
				
				printf ("Gamma: %0.2f\n",
						fGamma.As_real64 ());
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcImageUniqueID:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttAscii))
				return false;
				
			if (!CheckTagCount (parentCode, tagCode, tagCount, 33))
				return false;
			
			dng_string s;
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							s);
							
			if (s.Length () != 32)
				return false;
				
			dng_fingerprint f;
			
			for (uint32 j = 0; j < 32; j++)
				{
				
				char c = ForceUppercase (s.Get () [j]);
				
				uint32 digit;
				
				if (c >= '0' && c <= '9')
					{
					digit = c - '0';
					}
					
				else if (c >= 'A' && c <= 'F')
					{
					digit = c - 'A' + 10;
					}
					
				else
					return false;
				
				f.data [j >> 1] *= 16;
				f.data [j >> 1] += (uint8) digit;
					
				}
				
			fImageUniqueID = f;
			
			#if qDNGValidate

			if (gVerbose)
				{
								
				printf ("ImageUniqueID: ");
				
				DumpFingerprint (fImageUniqueID);
									
				printf ("\n");

				}
				
			#endif
			
			break;
			
			}

		case tcCameraOwnerNameExif:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fOwnerName);
							
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("CameraOwnerName: ");
				
				DumpString (fOwnerName);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcCameraSerialNumberExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fCameraSerialNumber);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (fCameraSerialNumber);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcLensSpecificationExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttRational);
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 4))
				return false;
				
			fLensInfo [0] = stream.TagValue_urational (tagType);
			fLensInfo [1] = stream.TagValue_urational (tagType);
			fLensInfo [2] = stream.TagValue_urational (tagType);
			fLensInfo [3] = stream.TagValue_urational (tagType);
			
			// Some third party software wrote zero rather than undefined values for
			// unknown entries. Work around this bug.
			
			for (uint32 j = 0; j < 4; j++)
				{
			
				if (fLensInfo [j].IsValid () && fLensInfo [j].As_real64 () <= 0.0)
					{
					
					fLensInfo [j] = dng_urational (0, 0);
					
					#if qDNGValidate
					
					ReportWarning ("Zero entry in LensSpecification tag--should be undefined");
					
					#endif

					}
					
				}
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("LensSpecificationExif: ");
				
				real64 minFL = fLensInfo [0].As_real64 ();
				real64 maxFL = fLensInfo [1].As_real64 ();
				
				if (minFL == maxFL)
					printf ("%0.1f mm", minFL);
				else
					printf ("%0.1f-%0.1f mm", minFL, maxFL);
					
				if (fLensInfo [2].d)
					{
					
					real64 minFS = fLensInfo [2].As_real64 ();
					real64 maxFS = fLensInfo [3].As_real64 ();
					
					if (minFS == maxFS)
						printf (" f/%0.1f", minFS);
					else
						printf (" f/%0.1f-%0.1f", minFS, maxFS);
					
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcLensMakeExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fLensMake);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (fLensMake);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcLensModelExif:
			{

			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fLensName);
							
			fLensNameWasReadFromExif = fLensName.NotEmpty ();
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (fLensName);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcLensSerialNumberExif:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fLensSerialNumber);
				
			#if qDNGValidate

			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (fLensSerialNumber);
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		default:
			{
			
			return false;
			
			}
			
		}
		
	return true;
	
	}
			
/*****************************************************************************/

// Parses tags that should only appear in GPS IFD

bool dng_exif::Parse_gps (dng_stream &stream,
						  dng_shared & /* shared */,
						  uint32 parentCode,
						  uint32 tagCode,
						  uint32 tagType,
						  uint32 tagCount,
						  uint64 /* tagOffset */)
	{
	
	switch (tagCode)
		{

		case tcGPSVersionID:
			{
			
			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 ();
			
			fGPSVersionID = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("GPSVersionID: %u.%u.%u.%u\n",
						(unsigned) b0,
						(unsigned) b1,
						(unsigned) b2,
						(unsigned) b3);
				}
				
			#endif

			break;
			
			}
			
		case tcGPSLatitudeRef:
		case tcGPSLongitudeRef:
		case tcGPSSatellites:
		case tcGPSStatus:
		case tcGPSMeasureMode:
		case tcGPSSpeedRef:
		case tcGPSTrackRef:
		case tcGPSImgDirectionRef:
		case tcGPSMapDatum:
		case tcGPSDestLatitudeRef:
		case tcGPSDestLongitudeRef:
		case tcGPSDestBearingRef:
		case tcGPSDestDistanceRef:
		case tcGPSDateStamp:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttAscii))
				return false;
			
			dng_string *s;
			
			switch (tagCode)
				{
				
				case tcGPSLatitudeRef:
					s = &fGPSLatitudeRef;
					break;
					
				case tcGPSLongitudeRef:
					s = &fGPSLongitudeRef;
					break;
					
				case tcGPSSatellites:
					s = &fGPSSatellites;
					break;
					
				case tcGPSStatus:
					s = &fGPSStatus;
					break;
					
				case tcGPSMeasureMode:
					s = &fGPSMeasureMode;
					break;

				case tcGPSSpeedRef:
					s = &fGPSSpeedRef;
					break;

				case tcGPSTrackRef:
					s = &fGPSTrackRef;
					break;

				case tcGPSImgDirectionRef:
					s = &fGPSImgDirectionRef;
					break;

				case tcGPSMapDatum:
					s = &fGPSMapDatum;
					break;
				
				case tcGPSDestLatitudeRef:
					s = &fGPSDestLatitudeRef;
					break;
				
				case tcGPSDestLongitudeRef:
					s = &fGPSDestLongitudeRef;
					break;
				
				case tcGPSDestBearingRef:
					s = &fGPSDestBearingRef;
					break;
				
				case tcGPSDestDistanceRef:
					s = &fGPSDestDistanceRef;
					break;
				
				case tcGPSDateStamp:
					s = &fGPSDateStamp;
					break;
					
				default:
					return false;
					
				}
				
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							*s);
							
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (*s);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcGPSLatitude:
		case tcGPSLongitude:
		case tcGPSTimeStamp:
		case tcGPSDestLatitude:
		case tcGPSDestLongitude:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttRational))
				return false;
			
			if (!CheckTagCount (parentCode, tagCode, tagCount, 3))
				return false;
			
			dng_urational *u;
			
			switch (tagCode)
				{
				
				case tcGPSLatitude:
					u = fGPSLatitude;
					break;
					
				case tcGPSLongitude:
					u = fGPSLongitude;
					break;
					
				case tcGPSTimeStamp:
					u = fGPSTimeStamp;
					break;
					
				case tcGPSDestLatitude:
					u = fGPSDestLatitude;
					break;
					
				case tcGPSDestLongitude:
					u = fGPSDestLongitude;
					break;

				default:
					return false;
					
				}
				
			u [0] = stream.TagValue_urational (tagType);
			u [1] = stream.TagValue_urational (tagType);
			u [2] = stream.TagValue_urational (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("%s:", LookupTagCode (parentCode, tagCode));
				
				for (uint32 j = 0; j < 3; j++)
					{
					
					if (u [j].d == 0)
						printf (" -");
						
					else
						printf (" %0.4f", u [j].As_real64 ());
						
					}
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcGPSAltitudeRef:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttByte);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fGPSAltitudeRef = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("GPSAltitudeRef: ");
				
				switch (fGPSAltitudeRef)
					{
					
					case 0:
						printf ("Sea level");
						break;
						
					case 1:
						printf ("Sea level reference (negative value)");
						break;
						
					default:
						printf ("%u", (unsigned) fGPSAltitudeRef);
						break;
						
					}
					
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}

		case tcGPSAltitude:
		case tcGPSDOP:
		case tcGPSSpeed:
		case tcGPSTrack:
		case tcGPSImgDirection:
		case tcGPSDestBearing:
		case tcGPSDestDistance:
		case tcGPSHPositioningError:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttRational))
				return false;
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			dng_urational *u;
			
			switch (tagCode)
				{
				
				case tcGPSAltitude:
					u = &fGPSAltitude;
					break;
					
				case tcGPSDOP:
					u = &fGPSDOP;
					break;
					
				case tcGPSSpeed:
					u = &fGPSSpeed;
					break;
	
				case tcGPSTrack:
					u = &fGPSTrack;
					break;
	
				case tcGPSImgDirection:
					u = &fGPSImgDirection;
					break;
				
				case tcGPSDestBearing:
					u = &fGPSDestBearing;
					break;
				
				case tcGPSDestDistance:
					u = &fGPSDestDistance;
					break;
				
				case tcGPSHPositioningError:
					u = &fGPSHPositioningError;
					break;
				
				default:
					return false;
					
				}
				
			*u = stream.TagValue_urational (tagType);

			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("%s:", LookupTagCode (parentCode, tagCode));
				
				if (u->d == 0)
					printf (" -");
					
				else
					printf (" %0.4f", u->As_real64 ());
					
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcGPSProcessingMethod:
		case tcGPSAreaInformation:
			{
			
			if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
				return false;
			
			dng_string *s;
			
			switch (tagCode)
				{
				
				case tcGPSProcessingMethod:
					s = &fGPSProcessingMethod;
					break;
					
				case tcGPSAreaInformation:
					s = &fGPSAreaInformation;
					break;
					
				default:
					return false;
					
				}
				
			ParseEncodedStringTag (stream,
								   parentCode,
								   tagCode,
				    			   tagCount,
				    		       *s);
					
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("%s: ", LookupTagCode (parentCode, tagCode));
				
				DumpString (*s);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}
			
		case tcGPSDifferential:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fGPSDifferential = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("GPSDifferential: ");
				
				switch (fGPSDifferential)
					{
					
					case 0:
						printf ("Measurement without differential correction");
						break;
						
					case 1:
						printf ("Differential correction applied");
						break;
						
					default:
						printf ("%u", (unsigned) fGPSDifferential);
						
					}
				
				printf ("\n");
				
				}
				
			#endif
			
			break;
			
			}
			
		default:
			{
			
			return false;
			
			}
			
		}
		
	return true;
	
	}
			
/*****************************************************************************/

// Parses tags that should only appear in Interoperability IFD

bool dng_exif::Parse_interoperability (dng_stream &stream,
						  			   dng_shared & /* shared */,
									   uint32 parentCode,
									   uint32 tagCode,
									   uint32 tagType,
									   uint32 tagCount,
									   uint64 /* tagOffset */)
	{
	
	switch (tagCode)
		{
		
		case tcInteroperabilityIndex:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			CheckTagCount (parentCode, tagCode, tagCount, 4);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fInteroperabilityIndex);
							
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("InteroperabilityIndex: ");
				
				DumpString (fInteroperabilityIndex);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}

		case tcInteroperabilityVersion:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttUndefined);
			
			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 ();
			
			fInteroperabilityVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				real64 x = (b0 - '0') * 10.00 +
						   (b1 - '0') *  1.00 +
						   (b2 - '0') *  0.10 +
						   (b3 - '0') *  0.01;
						   
				printf ("InteroperabilityVersion: %0.2f\n", x);
				
				}
				
			#endif
			
			break;
			
			}

		case tcRelatedImageFileFormat:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttAscii);
			
			ParseStringTag (stream,
							parentCode,
							tagCode,
							tagCount,
							fRelatedImageFileFormat);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				
				printf ("RelatedImageFileFormat: ");
				
				DumpString (fRelatedImageFileFormat);
				
				printf ("\n");
				
				}
				
			#endif
				
			break;
			
			}

		case tcRelatedImageWidth:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fRelatedImageWidth = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("RelatedImageWidth: %u\n", (unsigned) fRelatedImageWidth);
				}
				
			#endif
				
			break;
			
			}
		
		case tcRelatedImageLength:
			{
			
			CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
			
			CheckTagCount (parentCode, tagCode, tagCount, 1);
			
			fRelatedImageLength = stream.TagValue_uint32 (tagType);
			
			#if qDNGValidate
			
			if (gVerbose)
				{
				printf ("RelatedImageLength: %u\n", (unsigned) fRelatedImageLength);
				}
				
			#endif
				
			break;
			
			}
		
		default:
			{
			
			return false;
			
			}
			
		}
		
	return true;
	
	}
							   
/*****************************************************************************/

void dng_exif::PostParse (dng_host & /* host */,
						  dng_shared & /* shared */)
	{
	
	#if qDNGValidate
	
	const real64 kAPEX_Slop = 0.25;
	
	// Sanity check on MaxApertureValue.
		
	if (fMaxApertureValue.d)
		{
		
		real64 mav = fMaxApertureValue.As_real64 ();
		
		// Compare against ApertureValue or FNumber.
		
		real64 av = mav;
		
		if (fApertureValue.d)
			{
			
			av = fApertureValue.As_real64 ();
			
			}
			
		else if (fFNumber.d)
			{
			
			real64 fs = fFNumber.As_real64 ();
			
			if (fs >= 1.0)
				{
				
				av = FNumberToApertureValue (fs);
		
				}
			
			}
			
		if (mav > av + kAPEX_Slop)
			{
				
			ReportWarning ("MaxApertureValue conflicts with ApertureValue and/or FNumber");
						 
			}
			
		// Compare against LensInfo
		
		if (fLensInfo [2].d && fLensInfo [3].d)
			{
			
			real64 fs1 = fLensInfo [2].As_real64 ();
			real64 fs2 = fLensInfo [3].As_real64 ();
									
			if (fs1 >= 1.0 && fs2 >= 1.0 && fs2 >= fs1)
				{
				
				real64 av1 = FNumberToApertureValue (fs1);
				real64 av2 = FNumberToApertureValue (fs2);
				
				// Wide angle adapters might create an effective
				// wide FS, and tele-extenders always result
				// in a higher FS.
				
				if (mav < av1 - kAPEX_Slop - 1.0 ||
					mav > av2 + kAPEX_Slop + 2.0)
					{
						
					ReportWarning ("Possible MaxApertureValue conflict with LensInfo");
								 
					}
		
				}
			
			}
		
		}
		
	// Sanity check on FocalLength.
	
	if (fFocalLength.d)
		{
		
		real64 fl = fFocalLength.As_real64 ();
		
		if (fl < 1.0)
			{
			
			ReportWarning ("FocalLength is less than 1.0 mm (legal but unlikely)");
						 
			}
			
		else if (fLensInfo [0].d && fLensInfo [1].d)
			{
			
			real64 minFL = fLensInfo [0].As_real64 ();
			real64 maxFL = fLensInfo [1].As_real64 ();
			
			// Allow for wide-angle converters and tele-extenders.
			
			if (fl < minFL * 0.6 ||
			    fl > maxFL * 2.1)
				{
				
				ReportWarning ("Possible FocalLength conflict with LensInfo");
						 
				}
			
			}
		
		}
	
	#endif
	
	// Mirror DateTimeOriginal to DateTime.
	
	if (fDateTime.NotValid () && fDateTimeOriginal.IsValid ())
		{
		
		fDateTime = fDateTimeOriginal;
		
		}

	// Mirror EXIF 2.3 sensitivity tags to ISOSpeedRatings.

	if (fISOSpeedRatings [0] == 0 || fISOSpeedRatings [0] == 65535)
		{

		// Prefer Recommended Exposure Index, then Standard Output Sensitivity, then
		// ISO Speed, then Exposure Index.
		
		if (fRecommendedExposureIndex != 0 &&
			(fSensitivityType == stRecommendedExposureIndex ||
			 fSensitivityType == stSOSandREI				||
			 fSensitivityType == stREIandISOSpeed			||
			 fSensitivityType == stSOSandREIandISOSpeed))
			{
			
			fISOSpeedRatings [0] = fRecommendedExposureIndex;
			
			}
			
		else if (fStandardOutputSensitivity != 0 &&
				 (fSensitivityType == stStandardOutputSensitivity ||
				  fSensitivityType == stSOSandREI				  ||
				  fSensitivityType == stSOSandISOSpeed			  ||
				  fSensitivityType == stSOSandREIandISOSpeed))
			{
			
			fISOSpeedRatings [0] = fStandardOutputSensitivity;
			
			}

		else if (fISOSpeed != 0 &&
				 (fSensitivityType == stISOSpeed	   ||
				  fSensitivityType == stSOSandISOSpeed ||
				  fSensitivityType == stREIandISOSpeed ||
				  fSensitivityType == stSOSandREIandISOSpeed))
			{
			
			fISOSpeedRatings [0] = fISOSpeed;
			
			}
		
		}
	
	// Mirror ExposureIndex to ISOSpeedRatings.

	if (fExposureIndex.IsValid () && fISOSpeedRatings [0] == 0)
		{

		fISOSpeedRatings [0] = Round_uint32 (fExposureIndex.As_real64 ());

		}

	// Kodak sets the GPSAltitudeRef without setting the GPSAltitude.
	
	if (fGPSAltitude.NotValid ())
		{
		
		fGPSAltitudeRef = 0xFFFFFFFF;
		
		}
		
	// If there is no valid GPS data, clear the GPS version number.
	
	if (fGPSLatitude  [0].NotValid () &&
		fGPSLongitude [0].NotValid () &&
		fGPSAltitude     .NotValid () &&
		fGPSTimeStamp [0].NotValid () &&
		fGPSDateStamp    .IsEmpty  ())
		{
		
		fGPSVersionID = 0;
		
		}
		
	}
				   
/*****************************************************************************/