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

#include "dng_image_writer.h"

#include "dng_abort_sniffer.h"
#include "dng_area_task.h"
#include "dng_big_table.h"
#include "dng_bottlenecks.h"
#include "dng_camera_profile.h"
#include "dng_color_space.h"
#include "dng_exceptions.h"
#include "dng_exif.h"
#include "dng_flags.h"
#include "dng_gain_map.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_ifd.h"
#include "dng_image.h"
#include "dng_jpeg_image.h"
#include "dng_jxl.h"
#include "dng_lossless_jpeg.h"
#include "dng_memory.h"
#include "dng_memory_stream.h"
#include "dng_negative.h"
#include "dng_pixel_buffer.h"
#include "dng_preview.h"
#include "dng_read_image.h"
#include "dng_safe_arithmetic.h"
#include "dng_shared.h"
#include "dng_stream.h"
#include "dng_string_list.h"
#include "dng_tag_codes.h"
#include "dng_tag_values.h"
#include "dng_utils.h"

#if qDNGUseXMP
#include "dng_xmp.h"
#endif

#include "zlib.h"

#if qDNGUseLibJPEG
#include "dng_jpeglib.h"
#endif

#include <atomic>
	
/*****************************************************************************/

// Defines for testing DNG 1.2 features.

//#define qTestRowInterleave 2

//#define qTestSubTileBlockRows 2
//#define qTestSubTileBlockCols 2

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

// Defines for testing DNG 1.7 features.

//#define qTestColumnInterleave 2

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

dng_resolution::dng_resolution ()

	:	fXResolution ()
	,	fYResolution ()
	
	,	fResolutionUnit (0)
	
	{
	
	}

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

static void SpoolAdobeData (dng_stream &stream,
							const dng_metadata *metadata,
							const dng_jpeg_preview *preview,
							const dng_memory_block *imageResources)
	{
	
	TempBigEndian tempEndian (stream);
	
	#if qDNGUseXMP

	if (metadata && metadata->GetXMP ())
		{
		
		bool marked = false;
		
		if (metadata->GetXMP ()->GetBoolean (XMP_NS_XAP_RIGHTS,
											 "Marked",
											 marked))
			{
			
			stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
			stream.Put_uint16 (1034);
			stream.Put_uint16 (0);
			
			stream.Put_uint32 (1);
			
			stream.Put_uint8 (marked ? 1 : 0);
			
			stream.Put_uint8 (0);
			
			}
			
		dng_string webStatement;
		
		if (metadata->GetXMP ()->GetString (XMP_NS_XAP_RIGHTS,
											"WebStatement",
											webStatement))
			{
			
			dng_memory_data buffer;
			
			uint32 size = webStatement.Get_SystemEncoding (buffer);
			
			if (size > 0)
				{
				
				stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
				stream.Put_uint16 (1035);
				stream.Put_uint16 (0);
				
				stream.Put_uint32 (size);
				
				stream.Put (buffer.Buffer (), size);
				
				if (size & 1)
					stream.Put_uint8 (0);
					
				}
			
			}
		
		}
	
	#endif	// qDNGUseXMP
	
	if (preview)
		{
		
		preview->SpoolAdobeThumbnail (stream);
				
		}
		
	if (metadata && metadata->IPTCLength ())
		{
		
		dng_fingerprint iptcDigest = metadata->IPTCDigest ();

		if (iptcDigest.IsValid ())
			{
			
			stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
			stream.Put_uint16 (1061);
			stream.Put_uint16 (0);
			
			stream.Put_uint32 (16);
			
			stream.Put (iptcDigest.data, 16);
			
			}
			
		}
		
	if (imageResources)
		{
		
		uint32 size = imageResources->LogicalSize ();
		
		stream.Put (imageResources->Buffer (), size);
		
		if (size & 1)
			stream.Put_uint8 (0);
		
		}
		
	}

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

static dng_memory_block * BuildAdobeData (dng_host &host,
										  const dng_metadata *metadata,
										  const dng_jpeg_preview *preview,
										  const dng_memory_block *imageResources)
	{
	
	dng_memory_stream stream (host.Allocator ());
	
	SpoolAdobeData (stream,
					metadata,
					preview,
					imageResources);
					
	return stream.AsMemoryBlock (host.Allocator ());
	
	}

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

tag_string::tag_string (uint16 code,
						const dng_string &s,
						bool forceASCII)
						
	:	tiff_tag (code, ttAscii, 0)
	
	,	fString (s)
	
	{
			
	if (forceASCII)
		{
		
		// Metadata working group recommendation - go ahead
		// write UTF-8 into ASCII tag strings, rather than
		// actually force the strings to ASCII.	 There is a matching
		// change on the reading side to assume UTF-8 if the string
		// contains a valid UTF-8 string.
		//
		// fString.ForceASCII ();
		
		}
		
	else if (!fString.IsASCII ())
		{
		
		fType = ttByte;
		
		}
		
	fCount = fString.Length () + 1;
	
	}

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

void tag_string::Put (dng_stream &stream) const
	{
	
	stream.Put (fString.Get (), Size ());
	
	}

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

tag_encoded_text::tag_encoded_text (uint16 code,
									const dng_string &text)
	
	:	tiff_tag (code, ttUndefined, 0)
	
	,	fText (text)
	
	,	fUTF16 ()
	
	{
			
	if (fText.IsASCII ())
		{
	
		fCount = 8 + fText.Length ();
		
		}
		
	else
		{
		
		fCount = 8 + fText.Get_UTF16 (fUTF16) * 2;
		
		}
	
	}

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

void tag_encoded_text::Put (dng_stream &stream) const
	{
	
	if (fUTF16.Buffer ())
		{
		
		stream.Put ("UNICODE\000", 8);
		
		uint32 chars = (fCount - 8) >> 1;
		
		const uint16 *buf = fUTF16.Buffer_uint16 ();
		
		for (uint32 j = 0; j < chars; j++)
			{
			
			stream.Put_uint16 (buf [j]);
			
			}
		
		}
		
	else
		{
		
		stream.Put ("ASCII\000\000\000", 8);
		
		stream.Put (fText.Get (), fCount - 8);
		
		}
		
	}
	
/*****************************************************************************/

void tag_data_ptr::Put (dng_stream &stream) const
	{
	
	// If we are swapping bytes, we need to swap with the right size
	// entries.
	
	if (stream.SwapBytes ())
		{
	
		switch (Type ())
			{
			
			// Two byte entries.
			
			case ttShort:
			case ttSShort:
			case ttUnicode:
				{
				
				const uint16 *p = (const uint16 *) fData;
				
				uint32 entries = (Size () >> 1);
				
				for (uint32 j = 0; j < entries; j++)
					{
					
					stream.Put_uint16 (p [j]);
					
					}
				
				return;
				
				}
			
			// Four byte entries.
			
			case ttLong:
			case ttSLong:
			case ttRational:
			case ttSRational:
			case ttIFD:
			case ttFloat:
			case ttComplex:
				{
				
				const uint32 *p = (const uint32 *) fData;
				
				uint32 entries = (Size () >> 2);
				
				for (uint32 j = 0; j < entries; j++)
					{
					
					stream.Put_uint32 (p [j]);
					
					}
				
				return;
				
				}
				
			// Eight byte entries.
			
			case ttDouble:
			case ttLong8:
			case ttSLong8:
			case ttIFD8:
				{
				
				const real64 *p = (const real64 *) fData;
				
				uint32 entries = (Size () >> 3);
				
				for (uint32 j = 0; j < entries; j++)
					{
					
					stream.Put_real64 (p [j]);
					
					}
				
				return;
				
				}
			
			// Entries don't need to be byte swapped.  Fall through
			// to non-byte swapped case.
				
			default:
				{
				
				break;
				
				}

			}
			
		}
		
	// Non-byte swapped case.
		
	stream.Put (fData, Size ());
				
	}

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

void tag_big_uint::Put (dng_stream &stream) const
	{
	
	if (fType == ttLong)
		{
		
		if (fValue > 0xFFFFFFFF)
			{
			ThrowProgramError ("tag_big_uint overflow");
			}
			
		stream.Put_uint32 ((uint32) fValue);
		
		}
		
	else
		{
		stream.Put_uint64 (fValue);
		}
	
	}

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

void tag_big_uints::Put (dng_stream &stream) const
	{
	
	const uint64 *buffer = fData.Buffer_uint64 ();
	
	for (uint32 index = 0; index < fCount; index++)
		{
		
		uint64 x = buffer [index];
		
		if (fType == ttLong)
			{
			
			if (x > 0xFFFFFFFF)
				{
				ThrowProgramError ("tag_big_uints overflow");
				}
				
			stream.Put_uint32 ((uint32) x);
			
			}
			
		else
			{
			stream.Put_uint64 (x);
			}
			
		}
	
	}

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

tag_matrix::tag_matrix (uint16 code,
						const dng_matrix &m)
				   
	:	tag_srational_ptr (code, fEntry, m.Rows () * m.Cols ())
	
	{
	
	uint32 index = 0;
	
	for (uint32 r = 0; r < m.Rows (); r++)
		for (uint32 c = 0; c < m.Cols (); c++)
			{
			
			fEntry [index].Set_real64 (m [r] [c], 10000);
			
			index++;
			
			}
	
	}

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

tag_icc_profile::tag_icc_profile (const void *profileData,
								  uint32 profileSize)
			
	:	tag_data_ptr (tcICCProfile, 
					  ttUndefined,
					  0,
					  NULL)
	
	{
	
	if (profileData && profileSize)
		{
		
		SetCount (profileSize);
		SetData	 (profileData);
		
		}
	
	}
			
/******************************************************************************/

void tag_cfa_pattern::Put (dng_stream &stream) const
	{
	
	stream.Put_uint16 ((uint16) fCols);
	stream.Put_uint16 ((uint16) fRows);
	
	for (uint32 col = 0; col < fCols; col++)
		for (uint32 row = 0; row < fRows; row++)
			{
			
			stream.Put_uint8 (fPattern [row * kMaxCFAPattern + col]);
			
			}

	}

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

tag_exif_date_time::tag_exif_date_time (uint16 code,
										const dng_date_time &dt)
				   
	:	tag_data_ptr (code, ttAscii, 20, fData)
	
	{
	
	if (dt.IsValid ())
		{
	
		snprintf (fData,
				  20,
				  "%04d:%02d:%02d %02d:%02d:%02d",
				  (int) dt.fYear,
				  (int) dt.fMonth,
				  (int) dt.fDay,
				  (int) dt.fHour,
				  (int) dt.fMinute,
				  (int) dt.fSecond);
				 
		}
	
	}

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

tag_iptc::tag_iptc (const void *data,
					uint32 length)
	
	:	tiff_tag (tcIPTC_NAA, ttLong, (length + 3) >> 2)
	
	,	fData	(data  )
	,	fLength (length)
	
	{
	
	}

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

void tag_iptc::Put (dng_stream &stream) const
	{
	
	// Note: For historical compatibility reasons, the standard TIFF data 
	// type for IPTC data is ttLong, but without byte swapping.	 This really
	// should be ttUndefined, but doing the right thing would break some
	// existing readers.
	
	stream.Put (fData, fLength);
	
	// Pad with zeros to get to long word boundary.
	
	uint32 extra = fCount * 4 - fLength;
	
	while (extra--)
		{
		stream.Put_uint8 (0);
		}

	}

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

tag_xmp::tag_xmp (const dng_xmp *xmp)
	
	:	tag_uint8_ptr (tcXMP, NULL, 0)
	
	,	fBuffer ()
	
	{
	
	#if qDNGUseXMP
	
	if (xmp)
		{
		
		fBuffer.Reset (xmp->Serialize (true));
		
		if (fBuffer.Get ())
			{
			
			SetData (fBuffer->Buffer_uint8 ());
			
			SetCount (fBuffer->LogicalSize ());
			
			}
		
		}
	
	#endif	// qDNGUseXMP
	
	}

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

void dng_tiff_directory::Add (tiff_tag *tag)
	{
	
	// Tags must be sorted in increasing order of tag code.
	
	for (size_t j = 0; j < fTag.size (); j++)
		{
		
		if (tag->Code () < fTag [j]->Code ())
			{
			fTag.insert (fTag.begin () + j, tag);
			return;
			}
		
		}
		
	fTag.push_back (tag);
			
	}
			
/******************************************************************************/

void dng_tiff_directory::SetBigTIFF (bool isBigTIFF)
	{
	
	fBigTIFF = isBigTIFF;
	
	for (size_t index = 0; index < fTag.size (); index++)
		{
		
		fTag [index]->SetBigTIFF (isBigTIFF);
		
		}
	
	}
	
/******************************************************************************/

uint32 dng_tiff_directory::Size () const
	{
	
	if (!fTag.size ()) return 0;
	
	uint32 size = fBigTIFF ? (uint32) fTag.size () * 20 + 16
						   : (uint32) fTag.size () * 12 +  6;
	
	for (size_t index = 0; index < fTag.size (); index++)
		{
		
		uint32 tagSize = fTag [index]->Size ();
		
		if (tagSize > (uint32)(fBigTIFF ? 8 : 4))
			{
			
			size += (tagSize + 1) & ~1;
			
			}
		
		}
		
	return size;
	
	}
		
/******************************************************************************/

void dng_tiff_directory::Put (dng_stream &stream,
							  OffsetsBase offsetsBase,
							  uint64 explicitBase) const
	{
	
	if (!fTag.size ()) return;
	
	uint64 bigData = fBigTIFF ? fTag.size () * 20 + 16
							  : fTag.size () * 12 +	 6;
	
	if (offsetsBase == offsetsRelativeToStream)
		bigData += stream.Position ();

	else if (offsetsBase == offsetsRelativeToExplicitBase)
		bigData += explicitBase;

	if (fBigTIFF)
		stream.Put_uint64 ((uint64) fTag.size ());
	else
		stream.Put_uint16 ((uint16) fTag.size ());
	
	for (size_t index = 0; index < fTag.size (); index++)
		{
		
		const tiff_tag &tag = *fTag [index];
		
		stream.Put_uint16 (tag.Code	 ());
		stream.Put_uint16 (tag.Type	 ());
		
		if (fBigTIFF)
			stream.Put_uint64 ((uint64) tag.Count ());
		else
			stream.Put_uint32 ((uint32) tag.Count ());
		
		uint32 size = tag.Size ();
		
		if (size <= (uint32)(fBigTIFF ? 8 : 4))
			{
			
			tag.Put (stream);
			
			while (size < (uint32)(fBigTIFF ? 8 : 4))
				{
				stream.Put_uint8 (0);
				size++;
				}
				
			}
			
		else
			{
			
			if (fBigTIFF)
				stream.Put_uint64 (bigData);
			else
				stream.Put_uint32 ((uint32) bigData);
			
			bigData += (size + 1) & ~1;
						
			}
		
		}
		
	// Next IFD offset
		
	if (fBigTIFF)
		stream.Put_uint64 (fChained);
	else
		stream.Put_uint32 ((uint32) fChained);
	
	for (size_t index = 0; index < fTag.size (); index++)
		{
		
		const tiff_tag &tag = *fTag [index];
		
		uint32 size = tag.Size ();
		
		if (size > (uint32)(fBigTIFF ? 8 : 4))
			{
			
			tag.Put (stream);
			
			if (size & 1)
				stream.Put_uint8 (0);
			
			}
		
		}
	
	}

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

dng_basic_tag_set::dng_basic_tag_set (dng_tiff_directory &directory,
									  const dng_ifd &info)
						  
	:	fNewSubFileType (tcNewSubFileType, info.fNewSubFileType)
	
	,	fImageWidth	 (tcImageWidth , info.fImageWidth )
	,	fImageLength (tcImageLength, info.fImageLength)
	
	,	fPhotoInterpretation (tcPhotometricInterpretation,
							  (uint16) info.fPhotometricInterpretation)
	
	,	fFillOrder (tcFillOrder, 1)
	
	,	fSamplesPerPixel (tcSamplesPerPixel, (uint16) info.fSamplesPerPixel)
	
	,	fBitsPerSampleData (info.fSamplesPerPixel)
	
	,	fBitsPerSample (tcBitsPerSample,
						fBitsPerSampleData.data (),
						info.fSamplesPerPixel)
						
	,	fStrips (info.fUsesStrips)
						
	,	fTileWidth (tcTileWidth, info.fTileWidth)
	
	,	fTileLength (fStrips ? tcRowsPerStrip : tcTileLength, 
					 info.fTileLength)
	
	,	fTileOffsets (fStrips ? tcStripOffsets : tcTileOffsets,
					  info.TilesPerImage ())
					   
	,	fTileByteCounts (fStrips ? tcStripByteCounts : tcTileByteCounts,
						 info.TilesPerImage (),
						 info.fCompression != ccUncompressed)
						  
	,	fPlanarConfiguration (tcPlanarConfiguration, pcInterleaved)
	
	,	fCompression (tcCompression, (uint16) info.fCompression)
	,	fPredictor	 (tcPredictor  , (uint16) info.fPredictor  )
	
	,	fExtraSamplesData (info.fExtraSamplesCount)
	
	,	fExtraSamples (tcExtraSamples,
					   fExtraSamplesData.data (),
					   info.fExtraSamplesCount)
					   
	,	fSampleFormatData (info.fSamplesPerPixel)
					   
	,	fSampleFormat (tcSampleFormat,
					   fSampleFormatData.data (),
					   info.fSamplesPerPixel)
					   
	,	fRowInterleaveFactor (tcRowInterleaveFactor,
							  (uint16) info.fRowInterleaveFactor)

	,	fColumnInterleaveFactor (tcColumnInterleaveFactor,
								 (uint16) info.fColumnInterleaveFactor)

	,	fSubTileBlockSize (tcSubTileBlockSize,
						   fSubTileBlockSizeData,
						   2)
						   
	,	fJXLDistance    (tcJXLDistance)
	,	fJXLEffort      (tcJXLEffort)
	,	fJXLDecodeSpeed (tcJXLDecodeSpeed)
								 
	{
	
	uint32 j;
	
	for (j = 0; j < info.fSamplesPerPixel; j++)
		{
	
		fBitsPerSampleData [j] = (uint16) info.fBitsPerSample [0];
		
		}
	
	directory.Add (&fNewSubFileType);
	
	directory.Add (&fImageWidth);
	directory.Add (&fImageLength);
	
	directory.Add (&fPhotoInterpretation);
	
	directory.Add (&fSamplesPerPixel);
	
	directory.Add (&fBitsPerSample);
	
	if (info.fBitsPerSample [0] !=	8 &&
		info.fBitsPerSample [0] != 16 &&
		info.fBitsPerSample [0] != 32)
		{
	
		directory.Add (&fFillOrder);
		
		}
		
	if (!fStrips)
		{
		
		directory.Add (&fTileWidth);
	
		}

	directory.Add (&fTileLength);
	
	directory.Add (&fTileOffsets);
	directory.Add (&fTileByteCounts);
	
	directory.Add (&fPlanarConfiguration);
	
	directory.Add (&fCompression);
	
	if (info.fPredictor != cpNullPredictor)
		{
		
		directory.Add (&fPredictor);
		
		}
		
	if (info.fExtraSamplesCount != 0)
		{
		
		for (j = 0; j < info.fExtraSamplesCount; j++)
			{
			fExtraSamplesData [j] = (uint16) info.fExtraSamples [j];
			}
			
		directory.Add (&fExtraSamples);
		
		}
		
	if (info.fSampleFormat [0] != sfUnsignedInteger)
		{
		
		for (j = 0; j < info.fSamplesPerPixel; j++)
			{
			fSampleFormatData [j] = (uint16) info.fSampleFormat [j];
			}
			
		directory.Add (&fSampleFormat);
		
		}
		
	if (info.fRowInterleaveFactor != 1)
		{
		
		directory.Add (&fRowInterleaveFactor);
		
		}

	if (info.fColumnInterleaveFactor != 1)
		{
		
		directory.Add (&fColumnInterleaveFactor);
		
		}

	if (info.fSubTileBlockRows != 1 ||
		info.fSubTileBlockCols != 1)
		{
		
		fSubTileBlockSizeData [0] = (uint16) info.fSubTileBlockRows;
		fSubTileBlockSizeData [1] = (uint16) info.fSubTileBlockCols;
		
		directory.Add (&fSubTileBlockSize);
		
		}
		
	if (info.fCompression == ccJXL)
		{
		
		if (info.fJXLDistance >= 0.0f)
			{
			
			fJXLDistance.Set (info.fJXLDistance);
			
			directory.Add (&fJXLDistance);
			
			}
			
		if (info.fJXLEffort >= 1)
			{
			
			fJXLEffort.Set (info.fJXLEffort);
			
			directory.Add (&fJXLEffort);
			
			}
		
		if (info.fJXLDecodeSpeed >= 1)
			{
			
			fJXLDecodeSpeed.Set (info.fJXLDecodeSpeed);
			
			directory.Add (&fJXLDecodeSpeed);
			
			}
		
		}

	}

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

exif_tag_set::exif_tag_set (dng_tiff_directory &directory,
							const dng_exif &exif,
							bool makerNoteSafe,
							const void *makerNoteData,
							uint32 makerNoteLength,
							bool insideDNG)

	:	fExifIFD ()
	,	fGPSIFD	 ()
	
	,	fExifLink (tcExifIFD, 0)
	,	fGPSLink  (tcGPSInfo, 0)
	
	,	fAddedExifLink (false)
	,	fAddedGPSLink  (false)
	
	,	fExifVersion (tcExifVersion, ttUndefined, 4, fExifVersionData)
	
	,	fExposureTime	   (tcExposureTime	   , exif.fExposureTime		)
	,	fShutterSpeedValue (tcShutterSpeedValue, exif.fShutterSpeedValue)
	
	,	fFNumber	   (tcFNumber	   , exif.fFNumber		)
	,	fApertureValue (tcApertureValue, exif.fApertureValue)
	
	,	fBrightnessValue (tcBrightnessValue, exif.fBrightnessValue)
	
	,	fExposureBiasValue (tcExposureBiasValue, exif.fExposureBiasValue)
	
	,	fMaxApertureValue (tcMaxApertureValue , exif.fMaxApertureValue)
	
	,	fSubjectDistance (tcSubjectDistance, exif.fSubjectDistance)
	
	,	fFocalLength (tcFocalLength, exif.fFocalLength)
	
	// Special case: the EXIF 2.2 standard represents ISO speed ratings with 2 bytes,
	// which cannot hold ISO speed ratings above 65535 (e.g., 102400). In these
	// cases, we write the maximum representable ISO speed rating value in the EXIF
	// tag, i.e., 65535.

	,	fISOSpeedRatings (tcISOSpeedRatings, 
						  (uint16) Min_uint32 (65535, 
											   exif.fISOSpeedRatings [0]))

	,	fSensitivityType (tcSensitivityType, (uint16) exif.fSensitivityType)

	,	fStandardOutputSensitivity (tcStandardOutputSensitivity, exif.fStandardOutputSensitivity)
	
	,	fRecommendedExposureIndex (tcRecommendedExposureIndex, exif.fRecommendedExposureIndex)

	,	fISOSpeed (tcISOSpeed, exif.fISOSpeed)

	,	fISOSpeedLatitudeyyy (tcISOSpeedLatitudeyyy, exif.fISOSpeedLatitudeyyy)

	,	fISOSpeedLatitudezzz (tcISOSpeedLatitudezzz, exif.fISOSpeedLatitudezzz)

	,	fFlash (tcFlash, (uint16) exif.fFlash)
	
	,	fExposureProgram (tcExposureProgram, (uint16) exif.fExposureProgram)
	
	,	fMeteringMode (tcMeteringMode, (uint16) exif.fMeteringMode)
	
	,	fLightSource (tcLightSource, (uint16) exif.fLightSource)
	
	,	fSensingMethod (tcSensingMethodExif, (uint16) exif.fSensingMethod)
	
	,	fFocalLength35mm (tcFocalLengthIn35mmFilm, (uint16) exif.fFocalLengthIn35mmFilm)
	
	,	fFileSourceData ((uint8) exif.fFileSource)
	,	fFileSource		(tcFileSource, ttUndefined, 1, &fFileSourceData)

	,	fSceneTypeData ((uint8) exif.fSceneType)
	,	fSceneType	   (tcSceneType, ttUndefined, 1, &fSceneTypeData)
	
	,	fCFAPattern (tcCFAPatternExif,
					 exif.fCFARepeatPatternRows,
					 exif.fCFARepeatPatternCols,
					 &exif.fCFAPattern [0] [0])
	
	,	fCustomRendered		  (tcCustomRendered		 , (uint16) exif.fCustomRendered	  )
	,	fExposureMode		  (tcExposureMode		 , (uint16) exif.fExposureMode		  )
	,	fWhiteBalance		  (tcWhiteBalance		 , (uint16) exif.fWhiteBalance		  )
	,	fSceneCaptureType	  (tcSceneCaptureType	 , (uint16) exif.fSceneCaptureType	  )
	,	fGainControl		  (tcGainControl		 , (uint16) exif.fGainControl		  )
	,	fContrast			  (tcContrast			 , (uint16) exif.fContrast			  )
	,	fSaturation			  (tcSaturation			 , (uint16) exif.fSaturation		  )
	,	fSharpness			  (tcSharpness			 , (uint16) exif.fSharpness			  )
	,	fSubjectDistanceRange (tcSubjectDistanceRange, (uint16) exif.fSubjectDistanceRange)
		
	,	fDigitalZoomRatio (tcDigitalZoomRatio, exif.fDigitalZoomRatio)
	
	,	fExposureIndex (tcExposureIndexExif, exif.fExposureIndex)
	
	,	fImageNumber (tcImageNumber, exif.fImageNumber)
	
	,	fSelfTimerMode (tcSelfTimerMode, (uint16) exif.fSelfTimerMode)
	
	,	fBatteryLevelA (tcBatteryLevel, exif.fBatteryLevelA)
	,	fBatteryLevelR (tcBatteryLevel, exif.fBatteryLevelR)

	,	fColorSpace (tcColorSpace, (uint16) exif.fColorSpace)
	
	,	fFocalPlaneXResolution (tcFocalPlaneXResolutionExif, exif.fFocalPlaneXResolution)
	,	fFocalPlaneYResolution (tcFocalPlaneYResolutionExif, exif.fFocalPlaneYResolution)
	
	,	fFocalPlaneResolutionUnit (tcFocalPlaneResolutionUnitExif, (uint16) exif.fFocalPlaneResolutionUnit)
	
	,	fSubjectArea (tcSubjectArea, fSubjectAreaData, exif.fSubjectAreaCount)

	,	fLensInfo (tcLensInfo, fLensInfoData, 4)
	
	,	fDateTime		   (tcDateTime		   , exif.fDateTime			.DateTime ())
	,	fDateTimeOriginal  (tcDateTimeOriginal , exif.fDateTimeOriginal .DateTime ())
	,	fDateTimeDigitized (tcDateTimeDigitized, exif.fDateTimeDigitized.DateTime ())
	
	,	fSubsecTime			 (tcSubsecTime,			 exif.fDateTime			.Subseconds ())
	,	fSubsecTimeOriginal	 (tcSubsecTimeOriginal,	 exif.fDateTimeOriginal .Subseconds ())
	,	fSubsecTimeDigitized (tcSubsecTimeDigitized, exif.fDateTimeDigitized.Subseconds ())

	,	fOffsetTime			 (tcOffsetTime,			 exif.fDateTime			.OffsetTime ())
	,	fOffsetTimeOriginal	 (tcOffsetTimeOriginal,	 exif.fDateTimeOriginal .OffsetTime ())
	,	fOffsetTimeDigitized (tcOffsetTimeDigitized, exif.fDateTimeDigitized.OffsetTime ())

	,	fMake (tcMake, exif.fMake)

	,	fModel (tcModel, exif.fModel)
	
	,	fArtist (tcArtist, exif.fArtist)
	
	,	fSoftware (tcSoftware, exif.fSoftware)
	
	,	fCopyright (tcCopyright, exif.fCopyright)
	
	,	fImageDescription (tcImageDescription, exif.fImageDescription)
	
	,	fSerialNumber (tcCameraSerialNumber, exif.fCameraSerialNumber)

	,	fMakerNoteSafety (tcMakerNoteSafety, makerNoteSafe ? 1 : 0)

	,	fMakerNote (tcMakerNote, ttUndefined, makerNoteLength, makerNoteData)

	,	fUserComment (tcUserComment, exif.fUserComment)
	
	,	fImageUniqueID (tcImageUniqueID, ttAscii, 33, fImageUniqueIDData)

	// EXIF 2.3 tags.

	,	fCameraOwnerName   (tcCameraOwnerNameExif,	  exif.fOwnerName		  )
	,	fBodySerialNumber  (tcCameraSerialNumberExif, exif.fCameraSerialNumber)
	,	fLensSpecification (tcLensSpecificationExif,  fLensInfoData, 4		  )
	,	fLensMake		   (tcLensMakeExif,			  exif.fLensMake		  )
	,	fLensModel		   (tcLensModelExif,		  exif.fLensName		  )
	,	fLensSerialNumber  (tcLensSerialNumberExif,	  exif.fLensSerialNumber  )

	// EXIF 2.3.1 tags.

	,	fTemperature		  (tcTemperature,		   exif.fTemperature		 )
	,	fHumidity			  (tcHumidity,			   exif.fHumidity			 )
	,	fPressure			  (tcPressure,			   exif.fPressure			 )
	,	fWaterDepth			  (tcWaterDepth,		   exif.fWaterDepth			 )
	,	fAcceleration		  (tcAcceleration,		   exif.fAcceleration		 )
	,	fCameraElevationAngle (tcCameraElevationAngle, exif.fCameraElevationAngle)

	,	fGPSVersionID (tcGPSVersionID, fGPSVersionData, 4)
	
	,	fGPSLatitudeRef (tcGPSLatitudeRef, exif.fGPSLatitudeRef)
	,	fGPSLatitude	(tcGPSLatitude,	   exif.fGPSLatitude, 3)
	
	,	fGPSLongitudeRef (tcGPSLongitudeRef, exif.fGPSLongitudeRef)
	,	fGPSLongitude	 (tcGPSLongitude,	 exif.fGPSLongitude, 3)
	
	,	fGPSAltitudeRef (tcGPSAltitudeRef, (uint8) exif.fGPSAltitudeRef)
	,	fGPSAltitude	(tcGPSAltitude,			   exif.fGPSAltitude   )
	
	,	fGPSTimeStamp (tcGPSTimeStamp, exif.fGPSTimeStamp, 3)
		
	,	fGPSSatellites	(tcGPSSatellites , exif.fGPSSatellites )
	,	fGPSStatus		(tcGPSStatus	 , exif.fGPSStatus	   )
	,	fGPSMeasureMode (tcGPSMeasureMode, exif.fGPSMeasureMode)
	
	,	fGPSDOP (tcGPSDOP, exif.fGPSDOP)
		
	,	fGPSSpeedRef (tcGPSSpeedRef, exif.fGPSSpeedRef)
	,	fGPSSpeed	 (tcGPSSpeed   , exif.fGPSSpeed	  )
		
	,	fGPSTrackRef (tcGPSTrackRef, exif.fGPSTrackRef)
	,	fGPSTrack	 (tcGPSTrack   , exif.fGPSTrack	  )
		
	,	fGPSImgDirectionRef (tcGPSImgDirectionRef, exif.fGPSImgDirectionRef)
	,	fGPSImgDirection	(tcGPSImgDirection	 , exif.fGPSImgDirection   )
	
	,	fGPSMapDatum (tcGPSMapDatum, exif.fGPSMapDatum)
		
	,	fGPSDestLatitudeRef (tcGPSDestLatitudeRef, exif.fGPSDestLatitudeRef)
	,	fGPSDestLatitude	(tcGPSDestLatitude,	   exif.fGPSDestLatitude, 3)
	
	,	fGPSDestLongitudeRef (tcGPSDestLongitudeRef, exif.fGPSDestLongitudeRef)
	,	fGPSDestLongitude	 (tcGPSDestLongitude,	 exif.fGPSDestLongitude, 3)
	
	,	fGPSDestBearingRef (tcGPSDestBearingRef, exif.fGPSDestBearingRef)
	,	fGPSDestBearing	   (tcGPSDestBearing   , exif.fGPSDestBearing	)
		
	,	fGPSDestDistanceRef (tcGPSDestDistanceRef, exif.fGPSDestDistanceRef)
	,	fGPSDestDistance	(tcGPSDestDistance	 , exif.fGPSDestDistance   )
		
	,	fGPSProcessingMethod (tcGPSProcessingMethod, exif.fGPSProcessingMethod)
	,	fGPSAreaInformation	 (tcGPSAreaInformation , exif.fGPSAreaInformation )
	
	,	fGPSDateStamp (tcGPSDateStamp, exif.fGPSDateStamp)
	
	,	fGPSDifferential (tcGPSDifferential, (uint16) exif.fGPSDifferential)
		
	,	fGPSHPositioningError (tcGPSHPositioningError, exif.fGPSHPositioningError)
		
	{
	
	if (exif.fExifVersion)
		{
		
		fExifVersionData [0] = (uint8) (exif.fExifVersion >> 24);
		fExifVersionData [1] = (uint8) (exif.fExifVersion >> 16);
		fExifVersionData [2] = (uint8) (exif.fExifVersion >>  8);
		fExifVersionData [3] = (uint8) (exif.fExifVersion	   );
		
		fExifIFD.Add (&fExifVersion);

		}
	
	if (exif.fExposureTime.IsValid ())
		{
		fExifIFD.Add (&fExposureTime);
		}
		
	if (exif.fShutterSpeedValue.IsValid ())
		{
		fExifIFD.Add (&fShutterSpeedValue);
		}
		
	if (exif.fFNumber.IsValid ())
		{
		fExifIFD.Add (&fFNumber);
		}
		
	if (exif.fApertureValue.IsValid ())
		{
		fExifIFD.Add (&fApertureValue);
		}
		
	if (exif.fBrightnessValue.IsValid ())
		{
		fExifIFD.Add (&fBrightnessValue);
		}
		
	if (exif.fExposureBiasValue.IsValid ())
		{
		fExifIFD.Add (&fExposureBiasValue);
		}
		
	if (exif.fMaxApertureValue.IsValid ())
		{
		fExifIFD.Add (&fMaxApertureValue);
		}
		
	if (exif.fSubjectDistance.IsValid ())
		{
		fExifIFD.Add (&fSubjectDistance);
		}
		
	if (exif.fFocalLength.IsValid ())
		{
		fExifIFD.Add (&fFocalLength);
		}
	
	if (exif.fISOSpeedRatings [0] != 0)
		{
		fExifIFD.Add (&fISOSpeedRatings);
		}
		
	if (exif.fFlash <= 0x0FFFF)
		{
		fExifIFD.Add (&fFlash);
		}
		
	if (exif.fExposureProgram <= 0x0FFFF)
		{
		fExifIFD.Add (&fExposureProgram);
		}
		
	if (exif.fMeteringMode <= 0x0FFFF)
		{
		fExifIFD.Add (&fMeteringMode);
		}
		
	if (exif.fLightSource <= 0x0FFFF)
		{
		fExifIFD.Add (&fLightSource);
		}
		
	if (exif.fSensingMethod <= 0x0FFFF)
		{
		fExifIFD.Add (&fSensingMethod);
		}
		
	if (exif.fFocalLengthIn35mmFilm != 0)
		{
		fExifIFD.Add (&fFocalLength35mm);
		}
		
	if (exif.fFileSource <= 0x0FF)
		{
		fExifIFD.Add (&fFileSource);
		}
		
	if (exif.fSceneType <= 0x0FF)
		{
		fExifIFD.Add (&fSceneType);
		}
		
	if (exif.fCFARepeatPatternRows &&
		exif.fCFARepeatPatternCols)
		{
		fExifIFD.Add (&fCFAPattern);
		}
		
	if (exif.fCustomRendered <= 0x0FFFF)
		{
		fExifIFD.Add (&fCustomRendered);
		}
		
	if (exif.fExposureMode <= 0x0FFFF)
		{
		fExifIFD.Add (&fExposureMode);
		}
		
	if (exif.fWhiteBalance <= 0x0FFFF)
		{
		fExifIFD.Add (&fWhiteBalance);
		}
		
	if (exif.fSceneCaptureType <= 0x0FFFF)
		{
		fExifIFD.Add (&fSceneCaptureType);
		}
		
	if (exif.fGainControl <= 0x0FFFF)
		{
		fExifIFD.Add (&fGainControl);
		}
		
	if (exif.fContrast <= 0x0FFFF)
		{
		fExifIFD.Add (&fContrast);
		}
		
	if (exif.fSaturation <= 0x0FFFF)
		{
		fExifIFD.Add (&fSaturation);
		}
		
	if (exif.fSharpness <= 0x0FFFF)
		{
		fExifIFD.Add (&fSharpness);
		}
		
	if (exif.fSubjectDistanceRange <= 0x0FFFF)
		{
		fExifIFD.Add (&fSubjectDistanceRange);
		}
		
	if (exif.fDigitalZoomRatio.IsValid ())
		{
		fExifIFD.Add (&fDigitalZoomRatio);
		}
		
	if (exif.fExposureIndex.IsValid ())
		{
		fExifIFD.Add (&fExposureIndex);
		}
		
	if (insideDNG)	// TIFF-EP only tags
		{
		
		if (exif.fImageNumber != 0xFFFFFFFF)
			{
			directory.Add (&fImageNumber);
			}
			
		if (exif.fSelfTimerMode <= 0x0FFFF)
			{
			directory.Add (&fSelfTimerMode);
			}
			
		if (exif.fBatteryLevelA.NotEmpty ())
			{
			directory.Add (&fBatteryLevelA);
			}
			
		else if (exif.fBatteryLevelR.IsValid ())
			{
			directory.Add (&fBatteryLevelR);
			}
		
		}
		
	if (exif.fColorSpace == 1 ||
		exif.fColorSpace == 0xFFFF)
		{
		fExifIFD.Add (&fColorSpace);
		}
	
	if (exif.fFocalPlaneXResolution.IsValid ())
		{
		fExifIFD.Add (&fFocalPlaneXResolution);
		}
	
	if (exif.fFocalPlaneYResolution.IsValid ())
		{
		fExifIFD.Add (&fFocalPlaneYResolution);
		}
	
	if (exif.fFocalPlaneResolutionUnit <= 0x0FFFF)
		{
		fExifIFD.Add (&fFocalPlaneResolutionUnit);
		}
		
	if (exif.fSubjectAreaCount)
		{
		
		fSubjectAreaData [0] = (uint16) exif.fSubjectArea [0];
		fSubjectAreaData [1] = (uint16) exif.fSubjectArea [1];
		fSubjectAreaData [2] = (uint16) exif.fSubjectArea [2];
		fSubjectAreaData [3] = (uint16) exif.fSubjectArea [3];
		
		fExifIFD.Add (&fSubjectArea);
		
		}
	
	if (exif.fLensInfo [0].IsValid () &&
		exif.fLensInfo [1].IsValid ())
		{
		
		fLensInfoData [0] = exif.fLensInfo [0];
		fLensInfoData [1] = exif.fLensInfo [1];
		fLensInfoData [2] = exif.fLensInfo [2];
		fLensInfoData [3] = exif.fLensInfo [3];

		if (insideDNG)
			{
			directory.Add (&fLensInfo);
			}
		
		}
		
	if (exif.fDateTime.IsValid ())
		{
		
		directory.Add (&fDateTime);
		
		if (exif.fDateTime.Subseconds ().NotEmpty ())
			{
			fExifIFD.Add (&fSubsecTime);
			}
		
		}
		
	if (exif.fDateTimeOriginal.IsValid ())
		{
		
		fExifIFD.Add (&fDateTimeOriginal);
		
		if (exif.fDateTimeOriginal.Subseconds ().NotEmpty ())
			{
			fExifIFD.Add (&fSubsecTimeOriginal);
			}
		
		}
		
	if (exif.fDateTimeDigitized.IsValid ())
		{
		
		fExifIFD.Add (&fDateTimeDigitized);
		
		if (exif.fDateTimeDigitized.Subseconds ().NotEmpty ())
			{
			fExifIFD.Add (&fSubsecTimeDigitized);
			}
		
		}
		
	if (exif.fMake.NotEmpty ())
		{
		directory.Add (&fMake);
		}
		
	if (exif.fModel.NotEmpty ())
		{
		directory.Add (&fModel);
		}
		
	if (exif.fArtist.NotEmpty ())
		{
		directory.Add (&fArtist);
		}
	
	if (exif.fSoftware.NotEmpty ())
		{
		directory.Add (&fSoftware);
		}
	
	if (exif.fCopyright.NotEmpty ())
		{
		directory.Add (&fCopyright);
		}
	
	if (exif.fImageDescription.NotEmpty ())
		{
		directory.Add (&fImageDescription);
		}
	
	if (exif.fCameraSerialNumber.NotEmpty () && insideDNG)
		{
		directory.Add (&fSerialNumber);
		}
		
	if (makerNoteSafe && makerNoteData)
		{
		
		directory.Add (&fMakerNoteSafety);
		
		fExifIFD.Add (&fMakerNote);
		
		}
		
	if (exif.fUserComment.NotEmpty ())
		{
		fExifIFD.Add (&fUserComment);
		}
		
	if (exif.fImageUniqueID.IsValid ())
		{
		
		for (uint32 j = 0; j < 16; j++)
			{
			
			snprintf (fImageUniqueIDData + j * 2,
					  33,
					  "%02X",
					  (unsigned) exif.fImageUniqueID.data [j]);
					 
			}
		
		fExifIFD.Add (&fImageUniqueID);
		
		}

	if (exif.AtLeastVersion0230 ())
		{

		if (exif.fSensitivityType != 0)
			{
			
			fExifIFD.Add (&fSensitivityType);
			
			}

		// Sensitivity tags. Do not write these extra tags unless the SensitivityType
		// and PhotographicSensitivity (i.e., ISOSpeedRatings) values are valid.

		if (exif.fSensitivityType	  != 0 &&
			exif.fISOSpeedRatings [0] != 0)
			{

			// Standard Output Sensitivity (SOS).

			if (exif.fStandardOutputSensitivity != 0)
				{
				fExifIFD.Add (&fStandardOutputSensitivity);	
				}

			// Recommended Exposure Index (REI).

			if (exif.fRecommendedExposureIndex != 0)
				{
				fExifIFD.Add (&fRecommendedExposureIndex);
				}

			// ISO Speed.

			if (exif.fISOSpeed != 0)
				{

				fExifIFD.Add (&fISOSpeed);

				if (exif.fISOSpeedLatitudeyyy != 0 &&
					exif.fISOSpeedLatitudezzz != 0)
					{
						
					fExifIFD.Add (&fISOSpeedLatitudeyyy);
					fExifIFD.Add (&fISOSpeedLatitudezzz);
						
					}

				}

			}
		
		if (exif.fOwnerName.NotEmpty ())
			{
			fExifIFD.Add (&fCameraOwnerName);
			}
		
		if (exif.fCameraSerialNumber.NotEmpty ())
			{
			fExifIFD.Add (&fBodySerialNumber);
			}

		if (exif.fLensInfo [0].IsValid () &&
			exif.fLensInfo [1].IsValid ())
			{
			fExifIFD.Add (&fLensSpecification);
			}
		
		if (exif.fLensMake.NotEmpty ())
			{
			fExifIFD.Add (&fLensMake);
			}
		
		if (exif.fLensName.NotEmpty ())
			{
			fExifIFD.Add (&fLensModel);
			}
		
		if (exif.fLensSerialNumber.NotEmpty ())
			{
			fExifIFD.Add (&fLensSerialNumber);
			}
		
		}
  
	if (exif.AtLeastVersion0231 ())
		{
		
		if (exif.fDateTime.IsValid () &&
			exif.fDateTime.TimeZone ().IsValid ())
			{
			fExifIFD.Add (&fOffsetTime);
			}
		
		if (exif.fDateTimeOriginal.IsValid () &&
			exif.fDateTimeOriginal.TimeZone ().IsValid ())
			{
			fExifIFD.Add (&fOffsetTimeOriginal);
			}
		
		if (exif.fDateTimeDigitized.IsValid () &&
			exif.fDateTimeDigitized.TimeZone ().IsValid ())
			{
			fExifIFD.Add (&fOffsetTimeDigitized);
			}
			
		if (exif.fTemperature.IsValid ())
			{
			fExifIFD.Add (&fTemperature);
			}
		
		if (exif.fHumidity.IsValid ())
			{
			fExifIFD.Add (&fHumidity);
			}
		
		if (exif.fPressure.IsValid ())
			{
			fExifIFD.Add (&fPressure);
			}
		
		if (exif.fWaterDepth.IsValid ())
			{
			fExifIFD.Add (&fWaterDepth);
			}
		
		if (exif.fAcceleration.IsValid ())
			{
			fExifIFD.Add (&fAcceleration);
			}
		
		if (exif.fCameraElevationAngle.IsValid ())
			{
			fExifIFD.Add (&fCameraElevationAngle);
			}
		
		}
		
	if (exif.fGPSVersionID)
		{
		
		fGPSVersionData [0] = (uint8) (exif.fGPSVersionID >> 24);
		fGPSVersionData [1] = (uint8) (exif.fGPSVersionID >> 16);
		fGPSVersionData [2] = (uint8) (exif.fGPSVersionID >>  8);
		fGPSVersionData [3] = (uint8) (exif.fGPSVersionID	   );
		
		fGPSIFD.Add (&fGPSVersionID);
		
		}
		
	if (exif.fGPSLatitudeRef.NotEmpty () &&
		exif.fGPSLatitude [0].IsValid ())
		{
		fGPSIFD.Add (&fGPSLatitudeRef);
		fGPSIFD.Add (&fGPSLatitude	 );
		}
		
	if (exif.fGPSLongitudeRef.NotEmpty () &&
		exif.fGPSLongitude [0].IsValid ())
		{
		fGPSIFD.Add (&fGPSLongitudeRef);
		fGPSIFD.Add (&fGPSLongitude	  );
		}
		
	if (exif.fGPSAltitudeRef <= 0x0FF)
		{
		fGPSIFD.Add (&fGPSAltitudeRef);
		}
		
	if (exif.fGPSAltitude.IsValid ())
		{
		fGPSIFD.Add (&fGPSAltitude);
		}
		
	if (exif.fGPSTimeStamp [0].IsValid ())
		{
		fGPSIFD.Add (&fGPSTimeStamp);
		}
		
	if (exif.fGPSSatellites.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSSatellites);
		}
		
	if (exif.fGPSStatus.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSStatus);
		}
		
	if (exif.fGPSMeasureMode.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSMeasureMode);
		}
		
	if (exif.fGPSDOP.IsValid ())
		{
		fGPSIFD.Add (&fGPSDOP);
		}
		
	if (exif.fGPSSpeedRef.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSSpeedRef);
		}
		
	if (exif.fGPSSpeed.IsValid ())
		{
		fGPSIFD.Add (&fGPSSpeed);
		}
		
	if (exif.fGPSTrackRef.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSTrackRef);
		}
		
	if (exif.fGPSTrack.IsValid ())
		{
		fGPSIFD.Add (&fGPSTrack);
		}
		
	if (exif.fGPSImgDirectionRef.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSImgDirectionRef);
		}
		
	if (exif.fGPSImgDirection.IsValid ())
		{
		fGPSIFD.Add (&fGPSImgDirection);
		}

	if (exif.fGPSMapDatum.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSMapDatum);
		}
		
	if (exif.fGPSDestLatitudeRef.NotEmpty () &&
		exif.fGPSDestLatitude [0].IsValid ())
		{
		fGPSIFD.Add (&fGPSDestLatitudeRef);
		fGPSIFD.Add (&fGPSDestLatitude	 );
		}
		
	if (exif.fGPSDestLongitudeRef.NotEmpty () &&
		exif.fGPSDestLongitude [0].IsValid ())
		{
		fGPSIFD.Add (&fGPSDestLongitudeRef);
		fGPSIFD.Add (&fGPSDestLongitude	  );
		}
		
	if (exif.fGPSDestBearingRef.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSDestBearingRef);
		}
		
	if (exif.fGPSDestBearing.IsValid ())
		{
		fGPSIFD.Add (&fGPSDestBearing);
		}

	if (exif.fGPSDestDistanceRef.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSDestDistanceRef);
		}
		
	if (exif.fGPSDestDistance.IsValid ())
		{
		fGPSIFD.Add (&fGPSDestDistance);
		}
		
	if (exif.fGPSProcessingMethod.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSProcessingMethod);
		}

	if (exif.fGPSAreaInformation.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSAreaInformation);
		}
	
	if (exif.fGPSDateStamp.NotEmpty ())
		{
		fGPSIFD.Add (&fGPSDateStamp);
		}
	
	if (exif.fGPSDifferential <= 0x0FFFF)
		{
		fGPSIFD.Add (&fGPSDifferential);
		}

	if (exif.AtLeastVersion0230 ())
		{
		
		if (exif.fGPSHPositioningError.IsValid ())
			{
			fGPSIFD.Add (&fGPSHPositioningError);
			}

		}
		
	AddLinks (directory);
	
	}

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

void exif_tag_set::AddLinks (dng_tiff_directory &directory)
	{
	
	if (fExifIFD.Size () != 0 && !fAddedExifLink)
		{
		
		directory.Add (&fExifLink);
		
		fAddedExifLink = true;
		
		}
	
	if (fGPSIFD.Size () != 0 && !fAddedGPSLink)
		{
		
		directory.Add (&fGPSLink);
		
		fAddedGPSLink = true;
		
		}
	
	}
	
/******************************************************************************/

class range_tag_set
	{
	
	private:
	
		uint32 fActiveAreaData [4];
	
		tag_uint32_ptr fActiveArea;
	
		uint32 fMaskedAreaData [kMaxMaskedAreas * 4];
	
		tag_uint32_ptr fMaskedAreas;
							
		tag_uint16_ptr fLinearizationTable;
	
		uint16 fBlackLevelRepeatDimData [2];
		
		tag_uint16_ptr fBlackLevelRepeatDim;
		
		dng_urational fBlackLevelData [kMaxBlackPattern *
									   kMaxBlackPattern *
									   kMaxColorPlanes];
		
		tag_urational_ptr fBlackLevel;
		
		dng_memory_data fBlackLevelDeltaHData;
		dng_memory_data fBlackLevelDeltaVData;
		
		tag_srational_ptr fBlackLevelDeltaH;
		tag_srational_ptr fBlackLevelDeltaV;
		
		uint16 fWhiteLevelData16 [kMaxColorPlanes];
		uint32 fWhiteLevelData32 [kMaxColorPlanes];
		
		tag_uint16_ptr fWhiteLevel16;
		tag_uint32_ptr fWhiteLevel32;
		
	public:
	
		range_tag_set (dng_tiff_directory &directory,
					   const dng_negative &negative);
		
	};
	
/******************************************************************************/

range_tag_set::range_tag_set (dng_tiff_directory &directory,
							  const dng_negative &negative)
						  
	:	fActiveArea (tcActiveArea,
					 fActiveAreaData,
					 4)
	
	,	fMaskedAreas (tcMaskedAreas,
					  fMaskedAreaData,
					  0)
	
	,	fLinearizationTable (tcLinearizationTable,
							 NULL,
							 0)
						  
	,	fBlackLevelRepeatDim (tcBlackLevelRepeatDim,
							  fBlackLevelRepeatDimData,
							  2)
							 
	,	fBlackLevel (tcBlackLevel,
					 fBlackLevelData)
					 
	,	fBlackLevelDeltaHData ()
	,	fBlackLevelDeltaVData ()
					 
	,	fBlackLevelDeltaH (tcBlackLevelDeltaH)
	,	fBlackLevelDeltaV (tcBlackLevelDeltaV)
 
	,	fWhiteLevel16 (tcWhiteLevel,
					   fWhiteLevelData16)
					 
	,	fWhiteLevel32 (tcWhiteLevel,
					   fWhiteLevelData32)
					 
	{
	
	const dng_image &rawImage (negative.RawImage ());
	
	const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
	
	if (rangeInfo)
		{
		
		// ActiveArea:
		
			{
		
			const dng_rect &r = rangeInfo->fActiveArea;
			
			if (r.NotEmpty ())
				{
			
				fActiveAreaData [0] = r.t;
				fActiveAreaData [1] = r.l;
				fActiveAreaData [2] = r.b;
				fActiveAreaData [3] = r.r;
				
				directory.Add (&fActiveArea);
				
				}
				
			}
			
		// MaskedAreas:
			
		if (rangeInfo->fMaskedAreaCount)
			{
			
			fMaskedAreas.SetCount (rangeInfo->fMaskedAreaCount * 4);
			
			for (uint32 index = 0; index < rangeInfo->fMaskedAreaCount; index++)
				{
				
				const dng_rect &r = rangeInfo->fMaskedArea [index];
				
				fMaskedAreaData [index * 4 + 0] = r.t;
				fMaskedAreaData [index * 4 + 1] = r.l;
				fMaskedAreaData [index * 4 + 2] = r.b;
				fMaskedAreaData [index * 4 + 3] = r.r;
				
				}
				
			directory.Add (&fMaskedAreas);
			
			}
			
		// LinearizationTable:

		if (rangeInfo->fLinearizationTable.Get ())
			{
			
			fLinearizationTable.SetData	 (rangeInfo->fLinearizationTable->Buffer_uint16 ()	   );
			fLinearizationTable.SetCount (rangeInfo->fLinearizationTable->LogicalSize	() >> 1);
			
			directory.Add (&fLinearizationTable);
			
			}
			
		// BlackLevelRepeatDim:
		
			{
		
			fBlackLevelRepeatDimData [0] = (uint16) rangeInfo->fBlackLevelRepeatRows;
			fBlackLevelRepeatDimData [1] = (uint16) rangeInfo->fBlackLevelRepeatCols;
			
			directory.Add (&fBlackLevelRepeatDim);
			
			}
		
		// BlackLevel:
		
			{
		
			uint32 index = 0;
			
			for (uint16 v = 0; v < rangeInfo->fBlackLevelRepeatRows; v++)
				{
				
				for (uint32 h = 0; h < rangeInfo->fBlackLevelRepeatCols; h++)
					{
					
					for (uint32 c = 0; c < rawImage.Planes (); c++)
						{
					
						fBlackLevelData [index++] = rangeInfo->BlackLevel (v, h, c);
					
						}
						
					}
					
				}
				
			fBlackLevel.SetCount (rangeInfo->fBlackLevelRepeatRows *
								  rangeInfo->fBlackLevelRepeatCols * rawImage.Planes ());
			
			directory.Add (&fBlackLevel);
			
			}
		
		// BlackLevelDeltaH:
				
		if (rangeInfo->ColumnBlackCount ())
			{
			
			uint32 count = rangeInfo->ColumnBlackCount ();
		
			fBlackLevelDeltaHData.Allocate (count, sizeof (dng_srational));
												 
			dng_srational *blacks = (dng_srational *) fBlackLevelDeltaHData.Buffer ();
			
			for (uint32 col = 0; col < count; col++)
				{
				
				blacks [col] = rangeInfo->ColumnBlack (col);
				
				}
												 
			fBlackLevelDeltaH.SetData  (blacks);
			fBlackLevelDeltaH.SetCount (count );
			
			directory.Add (&fBlackLevelDeltaH);
			
			}
		
		// BlackLevelDeltaV:
				
		if (rangeInfo->RowBlackCount ())
			{
			
			uint32 count = rangeInfo->RowBlackCount ();
		
			fBlackLevelDeltaVData.Allocate (count, sizeof (dng_srational));
												 
			dng_srational *blacks = (dng_srational *) fBlackLevelDeltaVData.Buffer ();
			
			for (uint32 row = 0; row < count; row++)
				{
				
				blacks [row] = rangeInfo->RowBlack (row);
				
				}
												 
			fBlackLevelDeltaV.SetData  (blacks);
			fBlackLevelDeltaV.SetCount (count );
			
			directory.Add (&fBlackLevelDeltaV);
			
			}
			
		}
  
	else if (negative.RawImageBlackLevel ())
		{
		
		for (uint32 c = 0; c < rawImage.Planes (); c++)
			{
		
			fBlackLevelData [c] = dng_urational (negative.RawImageBlackLevel (), 1);
		
			}

		fBlackLevel.SetCount (rawImage.Planes ());
		
		directory.Add (&fBlackLevel);
			
		}
		
	// WhiteLevel:
	
	// Only use the 32-bit data type if we must use it since there
	// are some lazy (non-Adobe) DNG readers out there.
	
	bool needs32 = false;
		
	fWhiteLevel16.SetCount (rawImage.Planes ());
	fWhiteLevel32.SetCount (rawImage.Planes ());
	
	for (uint32 c = 0; c < fWhiteLevel16.Count (); c++)
		{
		
		fWhiteLevelData32 [c] = negative.WhiteLevel (c);
		
		if (fWhiteLevelData32 [c] > 0x0FFFF)
			{
			needs32 = true;
			}
			
		fWhiteLevelData16 [c] = (uint16) fWhiteLevelData32 [c];
		
		}
		
	if (needs32)
		{
		directory.Add (&fWhiteLevel32);
		}
		
	else
		{
		directory.Add (&fWhiteLevel16);
		}
	
	}

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

class mosaic_tag_set
	{
	
	private:
	
		uint16 fCFARepeatPatternDimData [2];
		
		tag_uint16_ptr fCFARepeatPatternDim;
										   
		uint8 fCFAPatternData [kMaxCFAPattern *
							   kMaxCFAPattern];
		
		tag_uint8_ptr fCFAPattern;
								 
		uint8 fCFAPlaneColorData [kMaxColorPlanes];
		
		tag_uint8_ptr fCFAPlaneColor;
									
		tag_uint16 fCFALayout;
		
		tag_uint32 fGreenSplit;
		
	public:
	
		mosaic_tag_set (dng_tiff_directory &directory,
						const dng_mosaic_info &info);
		
	};
	
/******************************************************************************/

mosaic_tag_set::mosaic_tag_set (dng_tiff_directory &directory,
								const dng_mosaic_info &info)

	:	fCFARepeatPatternDim (tcCFARepeatPatternDim,
							  fCFARepeatPatternDimData,
							  2)
							  
	,	fCFAPattern (tcCFAPattern,
					 fCFAPatternData)
					
	,	fCFAPlaneColor (tcCFAPlaneColor,
						fCFAPlaneColorData)
						
	,	fCFALayout (tcCFALayout,
					(uint16) info.fCFALayout)
	
	,	fGreenSplit (tcBayerGreenSplit,
					 info.fBayerGreenSplit)
	
	{
	
	if (info.IsColorFilterArray ())
		{
	
		// CFARepeatPatternDim:
		
		fCFARepeatPatternDimData [0] = (uint16) info.fCFAPatternSize.v;
		fCFARepeatPatternDimData [1] = (uint16) info.fCFAPatternSize.h;
				
		directory.Add (&fCFARepeatPatternDim);
		
		// CFAPattern:
		
		fCFAPattern.SetCount (info.fCFAPatternSize.v *
							  info.fCFAPatternSize.h);
							  
		for (int32 r = 0; r < info.fCFAPatternSize.v; r++)
			{
			
			for (int32 c = 0; c < info.fCFAPatternSize.h; c++)
				{
				
				fCFAPatternData [r * info.fCFAPatternSize.h + c] = info.fCFAPattern [r] [c];
				
				}
				
			}
				
		directory.Add (&fCFAPattern);
		
		// CFAPlaneColor:
		
		fCFAPlaneColor.SetCount (info.fColorPlanes);
		
		for (uint32 j = 0; j < info.fColorPlanes; j++)
			{
		
			fCFAPlaneColorData [j] = info.fCFAPlaneColor [j];
			
			}
		
		directory.Add (&fCFAPlaneColor);
		
		// CFALayout:
		
		fCFALayout.Set ((uint16) info.fCFALayout);
		
		directory.Add (&fCFALayout);
		
		// BayerGreenSplit:	 (only include if the pattern is a Bayer pattern)
			
		if (info.fCFAPatternSize == dng_point (2, 2) &&
			info.fColorPlanes	 == 3)
			{
			
			directory.Add (&fGreenSplit);
			
			}
			
		}

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

class color_tag_set
	{
	
	private:
	
		uint32 fColorChannels;
		
		tag_matrix fCameraCalibration1;
		tag_matrix fCameraCalibration2;
		tag_matrix fCameraCalibration3;
										 
		tag_string fCameraCalibrationSignature;
		
		tag_string fAsShotProfileName;

		dng_urational fAnalogBalanceData [4];
	
		tag_urational_ptr fAnalogBalance;
									
		dng_urational fAsShotNeutralData [4];
		
		tag_urational_ptr fAsShotNeutral;
										
		dng_urational fAsShotWhiteXYData [2];
		
		tag_urational_ptr fAsShotWhiteXY;
										
		tag_urational fLinearResponseLimit;
										  
	public:
	
		color_tag_set (dng_tiff_directory &directory,
					   const dng_negative &negative);
		
	};
	
/******************************************************************************/

color_tag_set::color_tag_set (dng_tiff_directory &directory,
							  const dng_negative &negative)
						  
	:	fColorChannels (negative.ColorChannels ())
	
	,	fCameraCalibration1 (tcCameraCalibration1,
							 negative.CameraCalibration1 ())
						
	,	fCameraCalibration2 (tcCameraCalibration2,
							 negative.CameraCalibration2 ())
							 
	,	fCameraCalibration3 (tcCameraCalibration3,
							 negative.CameraCalibration3 ())
							 
	,	fCameraCalibrationSignature (tcCameraCalibrationSignature,
									 negative.CameraCalibrationSignature ())
									 
	,	fAsShotProfileName (tcAsShotProfileName,
							negative.AsShotProfileName ())

	,	fAnalogBalance (tcAnalogBalance,
						fAnalogBalanceData,
						fColorChannels)
						
	,	fAsShotNeutral (tcAsShotNeutral,
						fAsShotNeutralData,
						fColorChannels)
						
	,	fAsShotWhiteXY (tcAsShotWhiteXY,
						fAsShotWhiteXYData,
						2)
									
	,	fLinearResponseLimit (tcLinearResponseLimit,
							  negative.LinearResponseLimitR ())
							  
	{
	
	if (fColorChannels > 1)
		{
		
		uint32 channels2 = fColorChannels * fColorChannels;
		
		if (fCameraCalibration1.Count () == channels2)
			{
			
			directory.Add (&fCameraCalibration1);
		
			}
			
		if (fCameraCalibration2.Count () == channels2)
			{
			
			directory.Add (&fCameraCalibration2);
		
			}
			
		if (fCameraCalibration3.Count () == channels2)
			{
			
			directory.Add (&fCameraCalibration3);
		
			}
			
		if (fCameraCalibration1.Count () == channels2 ||
			fCameraCalibration2.Count () == channels2 ||
			fCameraCalibration3.Count () == channels2)
			{
			
			if (negative.CameraCalibrationSignature ().NotEmpty ())
				{
				
				directory.Add (&fCameraCalibrationSignature);
				
				}
			
			}
			
		if (negative.AsShotProfileName ().NotEmpty ())
			{
			
			directory.Add (&fAsShotProfileName);
				
			}
			
		for (uint32 j = 0; j < fColorChannels; j++)
			{
			
			fAnalogBalanceData [j] = negative.AnalogBalanceR (j);
			
			}
			
		directory.Add (&fAnalogBalance);
		
		if (negative.HasCameraNeutral ())
			{
			
			for (uint32 k = 0; k < fColorChannels; k++)
				{
				
				fAsShotNeutralData [k] = negative.CameraNeutralR (k);
				
				}
			
			directory.Add (&fAsShotNeutral);
			
			}
			
		else if (negative.HasCameraWhiteXY ())
			{
			
			negative.GetCameraWhiteXY (fAsShotWhiteXYData [0],
									   fAsShotWhiteXYData [1]);
									   
			directory.Add (&fAsShotWhiteXY);
			
			}
		
		directory.Add (&fLinearResponseLimit);
		
		}
	
	}

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

class profile_tag_set
	{
	
	private:
	
		tag_uint16 fCalibrationIlluminant1;
		tag_uint16 fCalibrationIlluminant2;
		tag_uint16 fCalibrationIlluminant3;

		tag_data_ptr fIlluminantData1;
		tag_data_ptr fIlluminantData2;
		tag_data_ptr fIlluminantData3;

		tag_matrix fColorMatrix1;
		tag_matrix fColorMatrix2;
		tag_matrix fColorMatrix3;
		
		tag_matrix fForwardMatrix1;
		tag_matrix fForwardMatrix2;
		tag_matrix fForwardMatrix3;

		tag_matrix fReductionMatrix1;
		tag_matrix fReductionMatrix2;
		tag_matrix fReductionMatrix3;
		
		tag_string fProfileName;
		tag_string fProfileGroupName;
		
		tag_string fProfileCalibrationSignature;
		
		tag_uint32 fEmbedPolicyTag;
		
		tag_string fCopyrightTag;
		
		uint32 fHueSatMapDimData [3];
		
		tag_uint32_ptr fHueSatMapDims;

		tag_data_ptr fHueSatData1;
		tag_data_ptr fHueSatData2;
		tag_data_ptr fHueSatData3;
		
		tag_uint32 fHueSatMapEncodingTag;
		
		uint32 fLookTableDimData [3];
		
		tag_uint32_ptr fLookTableDims;

		tag_data_ptr fLookTableData;
		
		tag_uint32 fLookTableEncodingTag;

		tag_srational fBaselineExposureOffsetTag;
		
		tag_uint32 fDefaultBlackRenderTag;

		dng_memory_data fToneCurveBuffer;
		
		tag_data_ptr fToneCurveTag;

		tag_uint32 fToneMethodTag;

		AutoPtr<dng_memory_block> fIlluminantBlock1;
		AutoPtr<dng_memory_block> fIlluminantBlock2;
		AutoPtr<dng_memory_block> fIlluminantBlock3;

		AutoPtr<tag_owned_data_ptr> fProfileGainTableMapTag;

		AutoPtr<tag_owned_data_ptr> fProfileDynamicRangeTag;

		AutoPtr<tag_owned_data_ptr> fRGBTablesTag;

	public:
	
		profile_tag_set (dng_host &host,
						 dng_tiff_directory &directory,
						 const dng_camera_profile &profile);
		
	};
	
/******************************************************************************/

profile_tag_set::profile_tag_set (dng_host &host,
								  dng_tiff_directory &directory,
								  const dng_camera_profile &profile)
						  
	:	fCalibrationIlluminant1 (tcCalibrationIlluminant1,
								 (uint16) profile.CalibrationIlluminant1 ())
								 
	,	fCalibrationIlluminant2 (tcCalibrationIlluminant2,
								 (uint16) profile.CalibrationIlluminant2 ())
	
	,	fCalibrationIlluminant3 (tcCalibrationIlluminant3,
								 (uint16) profile.CalibrationIlluminant3 ())

	,	fIlluminantData1 (tcIlluminantData1,
						  ttUndefined,
						  profile.IlluminantData1 ().TagCount (),
						  nullptr)			 // specify later
		
	,	fIlluminantData2 (tcIlluminantData2,
						  ttUndefined,
						  profile.IlluminantData2 ().TagCount (),
						  nullptr)			 // specify later
		
	,	fIlluminantData3 (tcIlluminantData3,
						  ttUndefined,
						  profile.IlluminantData3 ().TagCount (),
						  nullptr)			 // specify later
		
	,	fColorMatrix1 (tcColorMatrix1,
					   profile.ColorMatrix1 ())
						
	,	fColorMatrix2 (tcColorMatrix2,
					   profile.ColorMatrix2 ())

	,	fColorMatrix3 (tcColorMatrix3,
					   profile.ColorMatrix3 ())

	,	fForwardMatrix1 (tcForwardMatrix1,
						 profile.ForwardMatrix1 ())
						
	,	fForwardMatrix2 (tcForwardMatrix2,
						 profile.ForwardMatrix2 ())
						
	,	fForwardMatrix3 (tcForwardMatrix3,
						 profile.ForwardMatrix3 ())
						
	,	fReductionMatrix1 (tcReductionMatrix1,
						   profile.ReductionMatrix1 ())
						
	,	fReductionMatrix2 (tcReductionMatrix2,
						   profile.ReductionMatrix2 ())
						   
	,	fReductionMatrix3 (tcReductionMatrix3,
						   profile.ReductionMatrix3 ())
						   
	,	fProfileName (tcProfileName,
					  profile.Name (),
					  false)
						   
	,	fProfileGroupName (tcProfileGroupName,
						   profile.GroupName (),
						   false)
						   
	,	fProfileCalibrationSignature (tcProfileCalibrationSignature,
									  profile.ProfileCalibrationSignature (),
									  false)
						
	,	fEmbedPolicyTag (tcProfileEmbedPolicy,
						 profile.EmbedPolicy ())
						 
	,	fCopyrightTag (tcProfileCopyright,
					   profile.Copyright (),
					   false)
					   
	,	fHueSatMapDims (tcProfileHueSatMapDims, 
						fHueSatMapDimData,
						3)
		
	,	fHueSatData1 (tcProfileHueSatMapData1,
					  ttFloat,
					  profile.HueSatDeltas1 ().DeltasCount () * 3,
					  profile.HueSatDeltas1 ().GetConstDeltas ())
					  
	,	fHueSatData2 (tcProfileHueSatMapData2,
					  ttFloat,
					  profile.HueSatDeltas2 ().DeltasCount () * 3,
					  profile.HueSatDeltas2 ().GetConstDeltas ())
					  
	,	fHueSatData3 (tcProfileHueSatMapData3,
					  ttFloat,
					  profile.HueSatDeltas3 ().DeltasCount () * 3,
					  profile.HueSatDeltas3 ().GetConstDeltas ())
					  
	,	fHueSatMapEncodingTag (tcProfileHueSatMapEncoding,
							   profile.HueSatMapEncoding ())
						 
	,	fLookTableDims (tcProfileLookTableDims,
						fLookTableDimData,
						3)
						
	,	fLookTableData (tcProfileLookTableData,
						ttFloat,
						profile.LookTable ().DeltasCount () * 3,
						profile.LookTable ().GetConstDeltas ())
					  
	,	fLookTableEncodingTag (tcProfileLookTableEncoding,
							   profile.LookTableEncoding ())
						 
	,	fBaselineExposureOffsetTag (tcBaselineExposureOffset,
									profile.BaselineExposureOffset ())
						 
	,	fDefaultBlackRenderTag (tcDefaultBlackRender,
								profile.DefaultBlackRender ())
						 
	,	fToneCurveBuffer ()
					  
	,	fToneCurveTag (tcProfileToneCurve,
					   ttFloat,
					   0,
					   NULL)
					   
	,	fToneMethodTag (tcProfileToneMethod,
						profile.ToneMethod ())

	{

	const uint32 illuminantModel = profile.IlluminantModel ();

	const bool isTripleIlluminantModel = (illuminantModel == 3);
	
	if (profile.HasColorMatrix1 ())
		{
	
		uint32 colorChannels = profile.ColorMatrix1 ().Rows ();
		
		directory.Add (&fCalibrationIlluminant1);

		if (profile.CalibrationIlluminant1 () == lsOther &&
			profile.IlluminantData1 ().IsValid ())
			{

			dng_memory_stream stream (host.Allocator ());

			auto &tag = fIlluminantData1;

			auto &block = fIlluminantBlock1;

			profile.IlluminantData1 ().Put (stream);

			block.Reset (stream.AsMemoryBlock (host.Allocator ()));

			DNG_REQUIRE (block->LogicalSize () >= tag.Count (),
						 "illuminant data block too small");
						 
			tag.SetData (block->Buffer ());
			
			directory.Add (&tag);
			
			}
		
		directory.Add (&fColorMatrix1);
		
		if (fForwardMatrix1.Count () == colorChannels * 3)
			{
			
			directory.Add (&fForwardMatrix1);

			}
		
		if (colorChannels > 3 && fReductionMatrix1.Count () == colorChannels * 3)
			{
			
			directory.Add (&fReductionMatrix1);
			
			}

		if (profile.HasColorMatrix2 ())
			{
		
			directory.Add (&fCalibrationIlluminant2);

			if (profile.CalibrationIlluminant2 () == lsOther &&
				profile.IlluminantData2 ().IsValid ())
				{

				dng_memory_stream stream (host.Allocator ());

				auto &tag = fIlluminantData2;

				auto &block = fIlluminantBlock2;

				profile.IlluminantData2 ().Put (stream);

				block.Reset (stream.AsMemoryBlock (host.Allocator ()));

				DNG_REQUIRE (block->LogicalSize () >= tag.Count (),
							 "illuminant data block too small");

				tag.SetData (block->Buffer ());

				directory.Add (&tag);

				}
		
			directory.Add (&fColorMatrix2);
				
			if (fForwardMatrix2.Count () == colorChannels * 3)
				{
				
				directory.Add (&fForwardMatrix2);

				}
		
			if (colorChannels > 3 && fReductionMatrix2.Count () == colorChannels * 3)
				{
				
				directory.Add (&fReductionMatrix2);
				
				}

			// Deal with 3rd illuminant.

			if (isTripleIlluminantModel)
				{
				
				directory.Add (&fCalibrationIlluminant3);

				if (profile.CalibrationIlluminant3 () == lsOther &&
					profile.IlluminantData3 ().IsValid ())
					{

					dng_memory_stream stream (host.Allocator ());

					auto &tag = fIlluminantData3;

					auto &block = fIlluminantBlock3;

					profile.IlluminantData3 ().Put (stream);

					block.Reset (stream.AsMemoryBlock (host.Allocator ()));

					DNG_REQUIRE (block->LogicalSize () >= tag.Count (),
								 "illuminant data block too small");

					tag.SetData (block->Buffer ());

					directory.Add (&tag);

					}

				directory.Add (&fColorMatrix3);

				if (fForwardMatrix3.Count () == colorChannels * 3)
					{

					directory.Add (&fForwardMatrix3);

					}

				if (colorChannels > 3 && fReductionMatrix3.Count () == colorChannels * 3)
					{

					directory.Add (&fReductionMatrix3);

					}

				} // 3rd illuminant
	
			}
			
		if (profile.Name ().NotEmpty ())
			{
			
			directory.Add (&fProfileName);

			}
			
		if (profile.GroupName ().NotEmpty ())
			{
			
			directory.Add (&fProfileGroupName);

			}
			
		if (profile.ProfileCalibrationSignature ().NotEmpty ())
			{
			
			directory.Add (&fProfileCalibrationSignature);
			
			}
			
		directory.Add (&fEmbedPolicyTag);
		
		if (profile.Copyright ().NotEmpty ())
			{
			
			directory.Add (&fCopyrightTag);
			
			}
		
		bool haveHueSat1 = profile.HueSatDeltas1 ().IsValid ();
		
		bool haveHueSat2 = profile.HueSatDeltas2 ().IsValid () &&
						   profile.HasColorMatrix2 ();

		bool haveHueSat3 = isTripleIlluminantModel &&
						   profile.HueSatDeltas3 ().IsValid ();

		if (haveHueSat1 || haveHueSat2 || haveHueSat3)
			{
			
			uint32 hueDivs = 0;
			uint32 satDivs = 0;
			uint32 valDivs = 0;

			// Get the hue sat map dimensions. The following logic only checks
			// the first two hue sat maps. Per the DNG 1.6 spec it is invalid
			// to have a 3rd hue sat map unless the first two are both
			// present, so it's not necessary to check the 3rd.

			if (haveHueSat1)
				{

				profile.HueSatDeltas1 ().GetDivisions (hueDivs,
													   satDivs,
													   valDivs);

				}

			else
				{

				profile.HueSatDeltas2 ().GetDivisions (hueDivs,
													   satDivs,
													   valDivs);

				}

			fHueSatMapDimData [0] = hueDivs;
			fHueSatMapDimData [1] = satDivs;
			fHueSatMapDimData [2] = valDivs;
			
			directory.Add (&fHueSatMapDims);

			// Don't bother including the ProfileHueSatMapEncoding tag unless it's
			// non-linear.

			if (profile.HueSatMapEncoding () != encoding_Linear)
				{

				directory.Add (&fHueSatMapEncodingTag);

				}
		
			}
			
		if (haveHueSat1)
			{
			
			directory.Add (&fHueSatData1);
			
			}
			
		if (haveHueSat2)
			{
			
			directory.Add (&fHueSatData2);
			
			}
			
		if (haveHueSat3)
			{
			
			directory.Add (&fHueSatData3);
			
			}
			
		if (profile.HasLookTable ())
			{
			
			uint32 hueDivs = 0;
			uint32 satDivs = 0;
			uint32 valDivs = 0;

			profile.LookTable ().GetDivisions (hueDivs,
											   satDivs,
											   valDivs);

			fLookTableDimData [0] = hueDivs;
			fLookTableDimData [1] = satDivs;
			fLookTableDimData [2] = valDivs;
			
			directory.Add (&fLookTableDims);
			
			directory.Add (&fLookTableData);
			
			// Don't bother including the ProfileLookTableEncoding tag unless it's
			// non-linear.

			if (profile.LookTableEncoding () != encoding_Linear)
				{

				directory.Add (&fLookTableEncodingTag);

				}
		
			}

		// Don't bother including the BaselineExposureOffset tag unless it's both
		// valid and non-zero.

		if (profile.BaselineExposureOffset ().IsValid ())
			{

			if (profile.BaselineExposureOffset ().As_real64 () != 0.0)
				{
			
				directory.Add (&fBaselineExposureOffsetTag);

				}
				
			}
			
		if (profile.DefaultBlackRender () != defaultBlackRender_Auto)
			{

			directory.Add (&fDefaultBlackRenderTag);

			}
		
		if (profile.ToneCurve ().IsValid ())
			{
			
			// Tone curve stored as pairs of 32-bit coordinates.  Probably could do with
			// 16-bits here, but should be small number of points so...
			
			uint32 toneCurvePoints = (uint32) (profile.ToneCurve ().fCoord.size ());

			fToneCurveBuffer.Allocate (dng_safe_uint32 (toneCurvePoints) * 2u,
									   sizeof (real32));

			real32 *points = fToneCurveBuffer.Buffer_real32 ();
			
			fToneCurveTag.SetCount (toneCurvePoints * 2);
			fToneCurveTag.SetData  (points);
			
			for (uint32 i = 0; i < toneCurvePoints; i++)
				{

				// Transpose coordinates so they are in a more expected
				// order (domain -> range).

				points [i * 2	 ] = (real32) profile.ToneCurve ().fCoord [i].h;
				points [i * 2 + 1] = (real32) profile.ToneCurve ().fCoord [i].v;

				}

			directory.Add (&fToneCurveTag);

			}

		if (profile.ToneMethod () != profileToneMethod_Unspecified)
			{

			directory.Add (&fToneMethodTag);

			}

		// ProfileGainTableMap2.

		if (profile.HasProfileGainTableMap ())
			{
			
			dng_memory_stream tempStream (host.Allocator (),
										  host.Sniffer ());

			auto pgtm = profile.ShareProfileGainTableMap ();

			// Always write version 2.

			pgtm->PutStream (tempStream, true);

			const_dng_memory_block_sptr block
				(tempStream.AsMemoryBlock (host.Allocator ()));
			
			fProfileGainTableMapTag.Reset
				(new tag_owned_data_ptr (tcProfileGainTableMap2,
										 ttUndefined,
										 block->LogicalSize (),
										 block));

			directory.Add (fProfileGainTableMapTag.Get ());

			}

		// ProfileDynamicRange.

		if (profile.DynamicRangeInfo ().IsValid () &&
			(profile.DynamicRangeInfo () != dng_camera_profile_dynamic_range ()))
			{
			
			dng_memory_stream tempStream (host.Allocator (),
										  host.Sniffer ());

			profile.DynamicRangeInfo ().PutStream (tempStream);

			const_dng_memory_block_sptr block
				(tempStream.AsMemoryBlock (host.Allocator ()));
			
			fProfileDynamicRangeTag.Reset
				(new tag_owned_data_ptr (tcProfileDynamicRange,
										 ttUndefined,
										 block->LogicalSize (),
										 block));

			directory.Add (fProfileDynamicRangeTag.Get ());
			
			}

		// RGBTables.
		
		if (profile.HasMaskedRGBTables () &&
			!profile.MaskedRGBTables ().IsNOP ())
			{

			dng_memory_stream tempStream (host.Allocator (),
										  host.Sniffer ());

			profile.MaskedRGBTables ().PutStream (tempStream);

			const_dng_memory_block_sptr block
				(tempStream.AsMemoryBlock (host.Allocator ()));
			
			fRGBTablesTag.Reset
				(new tag_owned_data_ptr (tcRGBTables,
										 ttUndefined,
										 block->LogicalSize (),
										 block));

			directory.Add (fRGBTablesTag.Get ());
			
			}

		} // HasColorMatrix1
	
	}

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

tiff_dng_extended_color_profile::tiff_dng_extended_color_profile 
								 (const dng_camera_profile &profile,
								  bool includeModelRestriction)

	:	fProfile (profile)
	
	,	fProfileTagSet ()
	
	,	fCameraModelTag (tcUniqueCameraModel,
						 fProfile.UniqueCameraModelRestriction ())

	{
	
	// Profile tags.
	
	dng_host host;
	
	fProfileTagSet.Reset (new profile_tag_set (host, *this, fProfile));
	
	// Camera this profile is for.

	if (includeModelRestriction)
		{
		
		if (fProfile.UniqueCameraModelRestriction ().NotEmpty ())
			{
			
			Add (&fCameraModelTag);
			
			}
					
		}
	
	}

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

tiff_dng_extended_color_profile::~tiff_dng_extended_color_profile ()
	{
		
	}

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

void tiff_dng_extended_color_profile::Put (dng_host & /* host */,
										   dng_stream &stream)
	{
	
	// Profile header.

	stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);

	stream.Put_uint16 (magicExtendedProfile);

	stream.Put_uint32 (8);
	
	// Write out directory.

	dng_tiff_directory::Put (stream, offsetsRelativeToExplicitBase, 8);

	}

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

uint64 tiff_dng_extended_color_profile::DataSize ()
	{
	
	return 8 + Size ();
	
	}
	
/*****************************************************************************/

tag_dng_noise_profile::tag_dng_noise_profile (const dng_noise_profile &profile)

	:	tag_data_ptr (tcNoiseProfile,
					  ttDouble,
					  2 * profile.NumFunctions (),
					  fValues)

	{

	DNG_REQUIRE (profile.NumFunctions () <= kMaxColorPlanes,
				 "Too many noise functions in tag_dng_noise_profile.");

	for (uint32 i = 0; i < profile.NumFunctions (); i++)
		{

		fValues [(2 * i)	] = profile.NoiseFunction (i).Scale	 ();
		fValues [(2 * i) + 1] = profile.NoiseFunction (i).Offset ();

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

class big_table_tag_set
	{
	
	private:
	
		const dng_big_table_dictionary &fDictionary;
	
		const dng_big_table_group_index &fGroupIndex;
	
		tag_data_ptr fBigTableDigests;
		tag_data_ptr fBigTableOffsets;
		tag_data_ptr fBigTableByteCounts;
		tag_data_ptr fBigTableGroupIndex;
		
		AutoPtr<dng_memory_block> fDigestsBuffer;
		AutoPtr<dng_memory_block> fOffsetsBuffer;
		AutoPtr<dng_memory_block> fByteCountsBuffer;
		AutoPtr<dng_memory_block> fGroupBuffer;

	public:
	
		big_table_tag_set (dng_host &host,
						   dng_tiff_directory &directory,
						   const dng_big_table_dictionary &dictionary,
						   const dng_big_table_group_index &groupIndex);
						   
		void WriteData (dng_stream &stream);
		
		uint64 DataSize ();
		
	};
	
/*****************************************************************************/

big_table_tag_set::big_table_tag_set (dng_host &host,
									  dng_tiff_directory &directory,
									  const dng_big_table_dictionary &dictionary,
									  const dng_big_table_group_index &groupIndex)
									  
	:	fDictionary (dictionary)

	,	fGroupIndex (groupIndex)
	
	,	fBigTableDigests (tcBigTableDigests,
						  ttByte,
						  0,
						  nullptr)
	
	,	fBigTableOffsets (tcBigTableOffsets,
						  ttLong,
						  0,
						  nullptr)
	
	,	fBigTableByteCounts (tcBigTableByteCounts,
							 ttLong,
							 0,
							 nullptr)
							 
	,	fBigTableGroupIndex (tcBigTableGroupIndex,
							 ttByte,
							 0,
							 nullptr)
							 
	{
	
	if (!fDictionary.IsEmpty ())
		{
		
		uint32 count = (uint32) fDictionary.Map ().size ();
		
		fDigestsBuffer.Reset (host.Allocate (count * 16));
		
		fBigTableDigests.SetCount (count * 16);
		fBigTableDigests.SetData  (fDigestsBuffer->Buffer_uint8 ());
		
		directory.Add (&fBigTableDigests);
		
		fOffsetsBuffer.Reset (host.Allocate (count * sizeof (uint32)));
		
		fBigTableOffsets.SetCount (count);
		fBigTableOffsets.SetData  (fOffsetsBuffer->Buffer_uint32 ());
		
		directory.Add (&fBigTableOffsets);
		
		fByteCountsBuffer.Reset (host.Allocate (count * sizeof (uint32)));
		
		fBigTableByteCounts.SetCount (count);
		fBigTableByteCounts.SetData	 (fByteCountsBuffer->Buffer_uint32 ());
	
		directory.Add (&fBigTableByteCounts);

		// Big table group index.

		if (!groupIndex.IsEmpty ())
			{

			const uint32 groups = (uint32) groupIndex.Map ().size ();

			constexpr uint32 bytesPerGroup = 2 * 16;

			const uint32 totalBytes = groups * bytesPerGroup;
			
			fGroupBuffer.Reset (host.Allocate (totalBytes));

			fBigTableGroupIndex.SetCount (totalBytes);
			fBigTableGroupIndex.SetData	 (fGroupBuffer->Buffer_uint8 ());

			directory.Add (&fBigTableGroupIndex);
			
			}

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

void big_table_tag_set::WriteData (dng_stream &stream)
	{
	
	if (!fDictionary.IsEmpty ())
		{
		
		uint32 index = 0;
		
		for (auto it = fDictionary.Map ().cbegin ();
			 it != fDictionary.Map ().cend ();
			 ++it)
			{
			
			fOffsetsBuffer->Buffer_uint32 () [index] = (uint32) stream.Position ();
			
			const dng_fingerprint &fingerprint = it->first;
			
			memcpy (fDigestsBuffer->Buffer_uint8 () + index * 16,
					fingerprint.data,
					16);
			
			const dng_ref_counted_block &block = it->second;
			
			uint32 count = block.LogicalSize ();
			
			fByteCountsBuffer->Buffer_uint32 () [index] = count;
			
			stream.Put (block.Buffer_uint8 (), count);
			
			stream.PadAlign2 ();
			
			index++;
			
			}

		// Group index.

		if (!fGroupIndex.IsEmpty ())
			{

			uint8 *dPtr = fGroupBuffer->Buffer_uint8 ();
			
			for (const auto &group : fGroupIndex.Map ())			
				{
				
				memcpy (dPtr	 , group.first .data, 16);
				memcpy (dPtr + 16, group.second.data, 16);

				dPtr += 32;
				
				}
			
			}

		}
	
	}

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

uint64 big_table_tag_set::DataSize ()
	{
	
	uint64 result = 0;
	
	if (!fDictionary.IsEmpty ())
		{
		
		for (auto it = fDictionary.Map ().cbegin ();
			 it != fDictionary.Map ().cend ();
			 ++it)
			{
			
			const dng_ref_counted_block &block = it->second;
			
			uint32 count = block.LogicalSize ();
			
			result += RoundUp2 (count);
			
			}
			
		}
		
	return result;
	
	}

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

dng_image_writer::dng_image_writer ()
	{
	
	}

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

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

uint32 dng_image_writer::CompressedBufferSize (const dng_ifd &ifd,
											   uint32 uncompressedSize)
	{

	const dng_safe_uint32 safeUncompressedSize (uncompressedSize);
	
	switch (ifd.fCompression)
		{
		
		case ccLZW:
			{
			
			// Add lots of slop for LZW to expand data.
				
			return (safeUncompressedSize * 2u + 1024u).Get ();
			
			}
			
		case ccDeflate:
			{
		
			// ZLib says maximum is source size + 0.1% + 12 bytes.

			const dng_safe_uint32 temp (uncompressedSize >> 8);
			
			return (safeUncompressedSize + temp + 64u).Get ();

			}
			
		case ccJPEG:
			{
			
			// If we are saving lossless JPEG from an 8-bit image, reserve
			// space to pad the data out to 16-bits.
			
			if (ifd.fBitsPerSample [0] <= 8)
				{

				return (safeUncompressedSize * 2u).Get ();
				
				}
				
			break;
	
			}
			
		default:
			break;
		
		}
	
	return 0;
	
	}
							
/******************************************************************************/

static void EncodeDelta8 (uint8 *dPtr,
						  uint32 rows,
						  uint32 cols,
						  uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = cols - 1; col > 0; col--)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

	}

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

static void EncodeDelta16 (uint16 *dPtr,
						   uint32 rows,
						   uint32 cols,
						   uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = cols - 1; col > 0; col--)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

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

static void EncodeDelta32 (uint32 *dPtr,
						   uint32 rows,
						   uint32 cols,
						   uint32 channels)
	{
	
	const uint32 dRowStep = cols * channels;
	
	for (uint32 row = 0; row < rows; row++)
		{
		
		for (uint32 col = cols - 1; col > 0; col--)
			{
			
			for (uint32 channel = 0; channel < channels; channel++)
				{
				
				dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
				
				}
			
			}
		
		dPtr += dRowStep;
		
		}

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

inline void EncodeDeltaBytes (uint8 *bytePtr, int32 cols, int32 channels)
	{
	
	if (channels == 1)
		{
		
		bytePtr += (cols - 1);
		
		uint8 this0 = bytePtr [0];
		
		for (int32 col = 1; col < cols; col++)
			{
			
			uint8 prev0 = bytePtr [-1];
			
			this0 -= prev0;
			
			bytePtr [0] = this0;
			
			this0 = prev0;
			
			bytePtr -= 1;

			}
	
		}
		
	else if (channels == 3)
		{
		
		bytePtr += (cols - 1) * 3;
		
		uint8 this0 = bytePtr [0];
		uint8 this1 = bytePtr [1];
		uint8 this2 = bytePtr [2];
		
		for (int32 col = 1; col < cols; col++)
			{
			
			uint8 prev0 = bytePtr [-3];
			uint8 prev1 = bytePtr [-2];
			uint8 prev2 = bytePtr [-1];
			
			this0 -= prev0;
			this1 -= prev1;
			this2 -= prev2;
			
			bytePtr [0] = this0;
			bytePtr [1] = this1;
			bytePtr [2] = this2;
			
			this0 = prev0;
			this1 = prev1;
			this2 = prev2;
			
			bytePtr -= 3;

			}
	
		}
		
	else
		{
	
		uint32 rowBytes = cols * channels;
		
		bytePtr += rowBytes - 1;
		
		for (uint32 col = channels; col < rowBytes; col++)
			{
			
			bytePtr [0] -= bytePtr [-channels];
				
			bytePtr--;

			}
			
		}

	}

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

static void EncodeFPDelta (uint8 *buffer,
						   uint8 *temp,
						   int32 cols,
						   int32 channels,
						   int32 bytesPerSample)
	{
	
	int32 rowIncrement = cols * channels;
	
	if (bytesPerSample == 2)
		{
		
		const uint8 *src = buffer;
		
		#if qDNGBigEndian
		uint8 *dst0 = temp;
		uint8 *dst1 = temp + rowIncrement;
		#else
		uint8 *dst1 = temp;
		uint8 *dst0 = temp + rowIncrement;
		#endif
				
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			dst0 [col] = src [0];
			dst1 [col] = src [1];
			
			src += 2;
			
			}
			
		}
		
	else if (bytesPerSample == 3)
		{
		
		const uint8 *src = buffer;
		
		uint8 *dst0 = temp;
		uint8 *dst1 = temp + rowIncrement;
		uint8 *dst2 = temp + rowIncrement * 2;
				
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			dst0 [col] = src [0];
			dst1 [col] = src [1];
			dst2 [col] = src [2];
			
			src += 3;
			
			}
			
		}
		
	else
		{
		
		const uint8 *src = buffer;
		
		#if qDNGBigEndian
		uint8 *dst0 = temp;
		uint8 *dst1 = temp + rowIncrement;
		uint8 *dst2 = temp + rowIncrement * 2;
		uint8 *dst3 = temp + rowIncrement * 3;
		#else
		uint8 *dst3 = temp;
		uint8 *dst2 = temp + rowIncrement;
		uint8 *dst1 = temp + rowIncrement * 2;
		uint8 *dst0 = temp + rowIncrement * 3;
		#endif
				
		for (int32 col = 0; col < rowIncrement; ++col)
			{
			
			dst0 [col] = src [0];
			dst1 [col] = src [1];
			dst2 [col] = src [2];
			dst3 [col] = src [3];
			
			src += 4;
			
			}
			
		}
		
	EncodeDeltaBytes (temp, cols*bytesPerSample, channels);
	
	memcpy (buffer, temp, cols*bytesPerSample*channels);
	
	}

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

void dng_image_writer::EncodePredictor (dng_host &host,
										const dng_ifd &ifd,
										dng_pixel_buffer &buffer,
										AutoPtr<dng_memory_block> &tempBuffer)
	{
	
	switch (ifd.fPredictor)
		{
		
		case cpHorizontalDifference:
		case cpHorizontalDifferenceX2:
		case cpHorizontalDifferenceX4:
			{
			
			int32 xFactor = 1;
			
			if (ifd.fPredictor == cpHorizontalDifferenceX2)
				{
				xFactor = 2;
				}
				
			else if (ifd.fPredictor == cpHorizontalDifferenceX4)
				{
				xFactor = 4;
				}
			
			switch (buffer.fPixelType)
				{
				
				case ttByte:
					{
					
					EncodeDelta8 ((uint8 *) buffer.fData,
								  buffer.fArea.H (),
								  buffer.fArea.W () / xFactor,
								  buffer.fPlanes	* xFactor);
					
					return;
					
					}
					
				case ttShort:
					{
					
					EncodeDelta16 ((uint16 *) buffer.fData,
								   buffer.fArea.H (),
								   buffer.fArea.W () / xFactor,
								   buffer.fPlanes	 * xFactor);
					
					return;
					
					}
					
				case ttLong:
					{
					
					EncodeDelta32 ((uint32 *) buffer.fData,
								   buffer.fArea.H (),
								   buffer.fArea.W () / xFactor,
								   buffer.fPlanes	 * xFactor);
					
					return;
					
					}
					
				default:
					break;
					
				}
			
			break;
			
			}
			
		case cpFloatingPoint:
		case cpFloatingPointX2:
		case cpFloatingPointX4:
			{
			
			int32 xFactor = 1;
			
			if (ifd.fPredictor == cpFloatingPointX2)
				{
				xFactor = 2;
				}
				
			else if (ifd.fPredictor == cpFloatingPointX4)
				{
				xFactor = 4;
				}
			
			if (buffer.fRowStep < 0)
				{
				ThrowProgramError ("Row step may not be negative");
				}

			dng_safe_uint32 tempBufferSize =
				dng_safe_uint32 (buffer.fPixelSize) * 
				static_cast<uint32> (buffer.fRowStep);

			if (!tempBuffer.Get () || 
				tempBuffer->LogicalSize () < tempBufferSize.Get ())
				{
				
				tempBuffer.Reset (host.Allocate (tempBufferSize.Get ()));
				
				}
				
			for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
				{
				
				EncodeFPDelta ((uint8 *) buffer.DirtyPixel (row, buffer.fArea.l, buffer.fPlane),
							   tempBuffer->Buffer_uint8 (),
							   buffer.fArea.W () / xFactor,
							   buffer.fPlanes	 * xFactor,
							   buffer.fPixelSize);
				
				}
			
			return;
			
			}
			
		default:
			break;
		
		}
	
	if (ifd.fPredictor != cpNullPredictor)
		{
		
		ThrowProgramError ();
		
		}
	
	}
							
/*****************************************************************************/

void dng_image_writer::ByteSwapBuffer (dng_host & /* host */,
									   dng_pixel_buffer &buffer)
	{
	
	uint32 pixels = buffer.fRowStep * buffer.fArea.H ();
	
	switch (buffer.fPixelSize)
		{
		
		case 2:
			{
			
			DoSwapBytes16 ((uint16 *) buffer.fData,
						   pixels);
						   
			break;
			
			}
			
		case 4:
			{
			
			DoSwapBytes32 ((uint32 *) buffer.fData,
						   pixels);
						   
			break;
			
			}
			
		default:
			break;
			
		}

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

void dng_image_writer::ReorderSubTileBlocks (const dng_ifd &ifd,
											 dng_pixel_buffer &buffer,
											 AutoPtr<dng_memory_block> &uncompressedBuffer,
											 AutoPtr<dng_memory_block> &subTileBlockBuffer)
	{
	
	uint32 blockRows = ifd.fSubTileBlockRows;
	uint32 blockCols = ifd.fSubTileBlockCols;
	
	uint32 rowBlocks = buffer.fArea.H () / blockRows;
	uint32 colBlocks = buffer.fArea.W () / blockCols;
	
	int32 rowStep = buffer.fRowStep * buffer.fPixelSize;
	int32 colStep = buffer.fColStep * buffer.fPixelSize;
	
	int32 rowBlockStep = rowStep * blockRows;
	int32 colBlockStep = colStep * blockCols;
	
	uint32 blockColBytes = blockCols * buffer.fPlanes * buffer.fPixelSize;
	
	const uint8 *s0 = uncompressedBuffer->Buffer_uint8 ();
		  uint8 *d0 = subTileBlockBuffer->Buffer_uint8 ();
	
	for (uint32 rowBlock = 0; rowBlock < rowBlocks; rowBlock++)
		{
		
		const uint8 *s1 = s0;
		
		for (uint32 colBlock = 0; colBlock < colBlocks; colBlock++)
			{
			
			const uint8 *s2 = s1;
			
			for (uint32 blockRow = 0; blockRow < blockRows; blockRow++)
				{
				
				for (uint32 j = 0; j < blockColBytes; j++)
					{
					
					d0 [j] = s2 [j];
					
					}
					
				d0 += blockColBytes;
				
				s2 += rowStep;
				
				}
			
			s1 += colBlockStep;
			
			}
			
		s0 += rowBlockStep;
		
		}
		
	// Copy back reordered pixels.
		
	DoCopyBytes (subTileBlockBuffer->Buffer		 (),
				 uncompressedBuffer->Buffer		 (),
				 uncompressedBuffer->LogicalSize ());
	
	}
							
/******************************************************************************/

class dng_lzw_compressor: private dng_uncopyable
	{
	
	private:
	
		enum
			{
			kResetCode = 256,
			kEndCode   = 257,
			kTableSize = 4096
			};

		// Compressor nodes have two son pointers.	The low order bit of
		// the next code determines which pointer is used.	This cuts the
		// number of nodes searched for the next code by two on average.

		struct LZWCompressorNode
			{
			int16 final;
			int16 son0;
			int16 son1;
			int16 brother;
			};
			
		dng_memory_data fBuffer;

		LZWCompressorNode *fTable;
		
		uint8 *fDstPtr;
		
		int32 fBitOffset;

		int32 fNextCode;
		
		int32 fCodeSize;
		
	public:
	
		dng_lzw_compressor ();
		
		void Compress (const uint8 *sPtr,
					   uint8 *dPtr,
					   uint32 sCount,
					   uint32 &dCount);
 
	private:
		
		void InitTable ();
	
		int32 SearchTable (int32 w, int32 k) const
			{
			
			DNG_ASSERT ((w >= 0) && (w <= kTableSize),
						"Bad w value in dng_lzw_compressor::SearchTable");
			
			int32 son0 = fTable [w] . son0;
			int32 son1 = fTable [w] . son1;
			
			// Branchless version of:
			// int32 code = (k & 1) ? son1 : son0;
			
			int32 code = son0 + ((-((int32) (k & 1))) & (son1 - son0));

			while (code > 0 && fTable [code].final != k)
				{
				code = fTable [code].brother;
				}

			return code;

			}

		void AddTable (int32 w, int32 k);
		
		void PutCodeWord (int32 code);

	};

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

dng_lzw_compressor::dng_lzw_compressor ()

	:	fBuffer	   ()
	,	fTable	   (NULL)
	,	fDstPtr	   (NULL)
	,	fBitOffset (0)
	,	fNextCode  (0)
	,	fCodeSize  (0)
	
	{
	
	fBuffer.Allocate (kTableSize, sizeof (LZWCompressorNode));
	
	fTable = (LZWCompressorNode *) fBuffer.Buffer ();
	
	}

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

void dng_lzw_compressor::InitTable ()
	{

	fCodeSize = 9;

	fNextCode = 258;
		
	LZWCompressorNode *node = &fTable [0];
	
	for (int32 code = 0; code < 256; ++code)
		{
		
		node->final	  = (int16) code;
		node->son0	  = -1;
		node->son1	  = -1;
		node->brother = -1;
		
		node++;
		
		}
		
	}

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

void dng_lzw_compressor::AddTable (int32 w, int32 k)
	{
	
	DNG_ASSERT ((w >= 0) && (w <= kTableSize),
				"Bad w value in dng_lzw_compressor::AddTable");

	LZWCompressorNode *node = &fTable [w];

	int32 nextCode = fNextCode;

	DNG_ASSERT ((nextCode >= 0) && (nextCode <= kTableSize),
				"Bad fNextCode value in dng_lzw_compressor::AddTable");
	
	LZWCompressorNode *node2 = &fTable [nextCode];
	
	fNextCode++;
	
	int32 oldSon;
	
	if( k&1 )
		{
		oldSon = node->son1;
		node->son1 = (int16) nextCode;
		}
	else
		{
		oldSon = node->son0;
		node->son0 = (int16) nextCode;
		}
	
	node2->final   = (int16) k;
	node2->son0	   = -1;
	node2->son1	   = -1;
	node2->brother = (int16) oldSon;
	
	if (nextCode == (1 << fCodeSize) - 1)
		{
		if (fCodeSize != 12)
			fCodeSize++;
		}
		
	}

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

void dng_lzw_compressor::PutCodeWord (int32 code)
	{
	
	int32 bit = (int32) (fBitOffset & 7);
	
	int32 offset1 = fBitOffset >> 3;
	int32 offset2 = (fBitOffset + fCodeSize - 1) >> 3;
		
	int32 shift1 = (fCodeSize + bit) -	8;
	int32 shift2 = (fCodeSize + bit) - 16;
	
	uint8 byte1 = (uint8) (code >> shift1);
	
	uint8 *dstPtr1 = fDstPtr + offset1;
	uint8 *dstPtr3 = fDstPtr + offset2;
	
	if (offset1 + 1 == offset2)
		{
		
		uint8 byte2 = (uint8) (code << (-shift2));
		
		if (bit)
			*dstPtr1 |= byte1;
		else
			*dstPtr1 = byte1;
		
		*dstPtr3 = byte2;
		
		}

	else
		{
		
		int32 shift3 = (fCodeSize + bit) - 24;
		
		uint8 byte2 = (uint8) (code >> shift2);
		uint8 byte3 = (uint8) (code << (-shift3));
		
		uint8 *dstPtr2 = fDstPtr + (offset1 + 1);
		
		if (bit)
			*dstPtr1 |= byte1;
		else
			*dstPtr1 = byte1;
		
		*dstPtr2 = byte2;
		
		*dstPtr3 = byte3;
		
		}
		
	fBitOffset += fCodeSize;
	
	}

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

void dng_lzw_compressor::Compress (const uint8 *sPtr,
								   uint8 *dPtr,
								   uint32 sCount,
								   uint32 &dCount)
	{
	
	fDstPtr = dPtr;
	
	fBitOffset = 0;
	
	InitTable ();
	
	PutCodeWord (kResetCode);
	
	int32 code = -1;
	
	int32 pixel;
	
	if (sCount > 0)
		{
		
		pixel = *sPtr;
		sPtr = sPtr + 1;
		code = pixel;

		sCount--;

		while (sCount--)
			{

			pixel = *sPtr;
			sPtr = sPtr + 1;
			
			int32 newCode = SearchTable (code, pixel);
			
			if (newCode == -1)
				{
				
				PutCodeWord (code);
				
				if (fNextCode < 4093)
					{
					AddTable (code, pixel);
					}
				else
					{
					PutCodeWord (kResetCode);
					InitTable ();
					}
					
				code = pixel;
				
				}
				
			else
				code = newCode;
				
			}
		
		}
		
	if (code != -1)
		{
		PutCodeWord (code);
		AddTable (code, 0);
		}
		
	PutCodeWord (kEndCode);

	dCount = (fBitOffset + 7) >> 3;

	}

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

#if qDNGUseLibJPEG

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

static void dng_error_exit (j_common_ptr cinfo)
	{
	
	// Output message.
	
	(*cinfo->err->output_message) (cinfo);
	
	// Convert to a dng_exception.

	switch (cinfo->err->msg_code)
		{
		
		case JERR_OUT_OF_MEMORY:
			{
			ThrowMemoryFull ();
			break;
			}
			
		default:
			{
			ThrowBadFormat ();
			}
			
		}
			
	}

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

static void dng_output_message (j_common_ptr cinfo)
	{
	
	// Format message to string.
	
	char buffer [JMSG_LENGTH_MAX];

	(*cinfo->err->format_message) (cinfo, buffer);
	
	// Report the libjpeg message as a warning.
	
	ReportWarning ("libjpeg", buffer);

	}

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

struct dng_jpeg_stream_dest
	{
	
	struct jpeg_destination_mgr pub;
	
	dng_stream *fStream;
	
	uint8 fBuffer [4096];
	
	};

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

static void dng_init_destination (j_compress_ptr cinfo)
	{
	
	dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;

	dest->pub.next_output_byte = dest->fBuffer;
	dest->pub.free_in_buffer   = sizeof (dest->fBuffer);
	
	}

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

static boolean dng_empty_output_buffer (j_compress_ptr cinfo)
	{
	
	dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;
	
	dest->fStream->Put (dest->fBuffer, sizeof (dest->fBuffer));

	dest->pub.next_output_byte = dest->fBuffer;
	dest->pub.free_in_buffer   = sizeof (dest->fBuffer);

	return TRUE;
	
	}

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

static void dng_term_destination (j_compress_ptr cinfo)
	{
	
	dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;
	
	uint32 datacount = sizeof (dest->fBuffer) -
					   (uint32) dest->pub.free_in_buffer;
	
	if (datacount)
		{
		dest->fStream->Put (dest->fBuffer, datacount);
		}

	}

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

static void jpeg_set_adobe_quality (struct jpeg_compress_struct *cinfo,
									int32 quality)
	{
	
	// If out of range, map to default.
		
	if (quality < 0 || quality > 12)
		{
		quality = 10;
		}
		
	// Adobe turns off chroma downsampling at high quality levels.
	
	bool useChromaDownsampling = (quality <= 6);
		
	// Approximate mapping from Adobe quality levels to LibJPEG levels.
	
	const int kLibJPEGQuality [13] =
		{
		5, 11, 23, 34, 46, 63, 76, 77, 86, 90, 94, 97, 99
		};
		
	quality = kLibJPEGQuality [quality];
	
	jpeg_set_quality (cinfo, quality, TRUE);
	
	// LibJPEG defaults to always using chroma downsampling.  Turn if off
	// if we need it off to match Adobe.
	
	if (!useChromaDownsampling)
		{
		
		cinfo->comp_info [0].h_samp_factor = 1;
		cinfo->comp_info [0].h_samp_factor = 1;
		
		}
				
	}

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

#endif	// qDNGUseLibJPEG

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

void dng_image_writer::WriteData (dng_host &host,
								  const dng_ifd &ifd,
								  dng_stream &stream,
								  dng_pixel_buffer &buffer,
								  AutoPtr<dng_memory_block> &compressedBuffer,
								  bool usingMultipleThreads)
	{

	(void) usingMultipleThreads;
	
	switch (ifd.fCompression)
		{
		
		case ccUncompressed:
			{
			
			// Special case support for when we save to 8-bits from
			// 16-bit data.
			
			if (ifd.fBitsPerSample [0] == 8 && buffer.fPixelType == ttShort)
				{
				
				uint32 count = buffer.fRowStep *
							   buffer.fArea.H ();
							   
				const uint16 *sPtr = (const uint16 *) buffer.fData;
				
				for (uint32 j = 0; j < count; j++)
					{
					
					stream.Put_uint8 ((uint8) sPtr [j]);
					
					}
				
				}
				
			else
				{
	
				// Swap bytes if required.
				
				if (stream.SwapBytes ())
					{
					
					ByteSwapBuffer (host, buffer);
										
					}
			
				// Write the bytes.
				
				stream.Put (buffer.fData, buffer.fRowStep *
										  buffer.fArea.H () *
										  buffer.fPixelSize);
										  
				}
			
			break;
			
			}
			
		case ccLZW:
		case ccDeflate:
			{
			
			// Both these compression algorithms are byte based.  The floating
			// point predictor already does byte ordering, so don't ever swap
			// when using it.
			
			if (stream.SwapBytes () && ifd.fPredictor != cpFloatingPoint)
				{
				
				ByteSwapBuffer (host,
								buffer);
								
				}
			
			// Run the compression algorithm.
				
			uint32 sBytes = buffer.fRowStep *
							buffer.fArea.H () *
							buffer.fPixelSize;
				
			uint8 *sBuffer = (uint8 *) buffer.fData;
				
			uint32 dBytes = 0;
				
			uint8 *dBuffer = compressedBuffer->Buffer_uint8 ();
			
			if (ifd.fCompression == ccLZW)
				{
				
				dng_lzw_compressor lzwCompressor;
				
				lzwCompressor.Compress (sBuffer,
										dBuffer,
										sBytes,
										dBytes);
										
				}
				
			else
				{
				
				uLongf dCount = compressedBuffer->LogicalSize ();
				
				int32 level = Z_DEFAULT_COMPRESSION;
				
				if (ifd.fCompressionQuality >= Z_BEST_SPEED &&
					ifd.fCompressionQuality <= Z_BEST_COMPRESSION)
					{
					
					level = ifd.fCompressionQuality;
					
					}
				
				int zResult = ::compress2 (dBuffer,
										   &dCount,
										   sBuffer,
										   sBytes,
										   level);
										  
				if (zResult != Z_OK)
					{
					
					ThrowMemoryFull ();
					
					}

				dBytes = (uint32) dCount;
				
				}
										
			if (dBytes > compressedBuffer->LogicalSize ())
				{
				
				ThrowOverflow ("Compression output buffer overflow");
				
				}
										
			stream.Put (dBuffer, dBytes);
				
			return;

			}
			
		case ccJPEG:
			{
			
			dng_pixel_buffer temp (buffer);
				
			if (buffer.fPixelType == ttByte)
				{
				
				// The lossless JPEG encoder needs 16-bit data, so if we are
				// are saving 8 bit data, we need to pad it out to 16-bits.
				
				temp.fData = compressedBuffer->Buffer ();
				
				temp.fPixelType = ttShort;
				temp.fPixelSize = 2;
				
				temp.CopyArea (buffer,
							   buffer.fArea,
							   buffer.fPlane,
							   buffer.fPlanes);
				
				}

			DoEncodeLosslessJPEG ((const uint16 *) temp.fData,
								  temp.fArea.H (),
								  temp.fArea.W (),
								  temp.fPlanes,
								  ifd.fBitsPerSample [0],
								  temp.fRowStep,
								  temp.fColStep,
								  stream);
										
			break;
			
			}
			
		#if qDNGUseLibJPEG
		
		case ccLossyJPEG:
			{
			
			struct jpeg_compress_struct cinfo;
	
			// Setup the error manager.
			
			struct jpeg_error_mgr jerr;

			cinfo.err = jpeg_std_error (&jerr);
			
			jerr.error_exit		= dng_error_exit;
			jerr.output_message = dng_output_message;
	
			try
				{
				
				// Create the compression context.

				jpeg_create_compress (&cinfo);
				
				// Setup the destination manager to write to stream.
				
				dng_jpeg_stream_dest dest;
				
				dest.fStream = &stream;
				
				dest.pub.init_destination	 = dng_init_destination;
				dest.pub.empty_output_buffer = dng_empty_output_buffer;
				dest.pub.term_destination	 = dng_term_destination;
				
				cinfo.dest = &dest.pub;
				
				// Setup basic image info.
				
				cinfo.image_width	   = buffer.fArea.W ();
				cinfo.image_height	   = buffer.fArea.H ();
				cinfo.input_components = buffer.fPlanes;
				
				switch (buffer.fPlanes)
					{
					
					case 1:
						cinfo.in_color_space = JCS_GRAYSCALE;
						break;
						
					case 3:
						cinfo.in_color_space = JCS_RGB;
						break;
						
					case 4:
						cinfo.in_color_space = JCS_CMYK;
						break;
						
					default:
						ThrowProgramError ();
						
					}
					
				// Setup the compression parameters.

				jpeg_set_defaults (&cinfo);
				
				jpeg_set_adobe_quality (&cinfo, ifd.fCompressionQuality);
				
				// Write the JPEG header.
				
				jpeg_start_compress (&cinfo, TRUE);
				
				// Write the scanlines.
				
				for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
					{
					
					uint8 *sampArray [1];
		
					sampArray [0] = buffer.DirtyPixel_uint8 (row,
															 buffer.fArea.l,
															 0);

					jpeg_write_scanlines (&cinfo, sampArray, 1);
					
					}

				// Cleanup.
					
				jpeg_finish_compress (&cinfo);

				jpeg_destroy_compress (&cinfo);
					
				}
				
			catch (...)
				{
				
				jpeg_destroy_compress (&cinfo);
				
				throw;
				
				}
				
			return;
			
			}
			
		#endif	// qDNGUseLibJPEG

		case ccJXL:
			{

			dng_jxl_encode_settings settings;

			if (ifd.fJXLEncodeSettings)
				settings = *ifd.fJXLEncodeSettings;

			else if (host.JXLEncodeSettings ())
				settings = *host.JXLEncodeSettings ();

			else
				{
				DNG_REPORT ("No JXLEncodeSettings, encoding tile using default settings");
				}

			#if 0
			printf ("jxl encode tile: distance=%0.2f, effort=%d\n",
					settings.Distance (),
					settings.Effort ());
			#endif

			// Set concurrency. If we're already using multiple threads (outer
			// loop processes a tile per thread), then tell the jxl encoder to
			// use a single thread.

			settings.SetSingleThread (usingMultipleThreads);

			// Get color space info.

			dng_jxl_color_space_info colorSpaceInfoLocal;

			const dng_jxl_color_space_info *colorSpaceInfoPtr =
				&colorSpaceInfoLocal;

			// If host specified a custom value, then use that.
			
			if (host.JXLColorSpaceInfo ())
				{
				
				colorSpaceInfoPtr = host.JXLColorSpaceInfo ();

				}

			// Else get it from the IFD.
			
			else if (ifd.fJXLColorEncoding)
				{
				
				colorSpaceInfoLocal.fJxlColorEncoding.Reset
					(new JxlColorEncoding (*ifd.fJXLColorEncoding));

				}

			else
				{
				
				PreviewColorSpaceToJXLEncoding (ifd.fPreviewInfo.fColorSpace,
												ifd.fSamplesPerPixel,
												colorSpaceInfoLocal);
				
				}

			#if 0
			printf ("jxl: strips=%s, tiles=%s, ta=%u, td=%u\n",
					ifd.fUsesStrips ? "yes" : "no",
					ifd.fUsesTiles ? "yes" : "no",
					ifd.TilesAcross (),
					ifd.TilesDown ());
			#endif

			// If we're writing a single-tile preview, then write the JXL
			// using the container format (i.e., with boxes) so that readers
			// have the option of extracting a full standalone JXL file by
			// simply copying the bytes.

			if (ifd.TilesAcross () == 1 &&
				ifd.TilesDown   () == 1 &&
				(uint32 (ifd.fPreviewInfo.fColorSpace) <=
				 uint32 (previewColorSpace_LastValid)))
				{

				#if 0
				printf ("--- writing JXL container\n");
				#endif

				EncodeJXL_Container (host,
									 stream,
									 buffer,
									 settings,
									 *colorSpaceInfoPtr,
									 nullptr,
									 false,
									 false,
									 false,
									 nullptr);

				}

			// Otherwise write a bare JXL codestream, which saves a few bytes
			// per tile.

			else
				{

				EncodeJXL_Tile (host,
								stream,
								buffer,
								*colorSpaceInfoPtr,
								settings);

				}
			
			return;
			
			}

		default:
			{
			
			ThrowProgramError ();
			
			}
			
		}
	
	}
							
/******************************************************************************/

void dng_image_writer::EncodeJPEGPreview (dng_host &host,
										  const dng_image &image,
										  dng_jpeg_preview &preview,
										  int32 quality)
	{
	
	#if qDNGUseLibJPEG
		
	dng_memory_stream stream (host.Allocator ());
	
	struct jpeg_compress_struct cinfo;

	// Setup the error manager.
	
	struct jpeg_error_mgr jerr;

	cinfo.err = jpeg_std_error (&jerr);
	
	jerr.error_exit		= dng_error_exit;
	jerr.output_message = dng_output_message;

	try
		{
		
		// Create the compression context.

		jpeg_create_compress (&cinfo);
		
		// Setup the destination manager to write to stream.
		
		dng_jpeg_stream_dest dest;
		
		dest.fStream = &stream;
		
		dest.pub.init_destination	 = dng_init_destination;
		dest.pub.empty_output_buffer = dng_empty_output_buffer;
		dest.pub.term_destination	 = dng_term_destination;
		
		cinfo.dest = &dest.pub;
		
		// Setup basic image info.
		
		cinfo.image_width	   = image.Bounds ().W ();
		cinfo.image_height	   = image.Bounds ().H ();
		cinfo.input_components = image.Planes ();
		
		switch (image.Planes ())
			{
			
			case 1:
				cinfo.in_color_space = JCS_GRAYSCALE;
				break;
				
			case 3:
				cinfo.in_color_space = JCS_RGB;
				break;
				
			default:
				ThrowProgramError ();
				
			}
			
		// Setup the compression parameters.

		jpeg_set_defaults (&cinfo);
		
		jpeg_set_adobe_quality (&cinfo, quality);
		
		// Find some preview information based on the compression settings.
		
		preview.SetIFDInfo (host, image);
	
		if (image.Planes () == 3)
			{
			
			preview.SetYCbCr (cinfo.comp_info [0].h_samp_factor,
							  cinfo.comp_info [0].v_samp_factor);
			
			}
		
		// Write the JPEG header.
		
		jpeg_start_compress (&cinfo, TRUE);
		
		// Write the scanlines.
		
		dng_pixel_buffer buffer (image.Bounds (), 
								 0, 
								 image.Planes (), 
								 ttByte,
								 pcInterleaved, 
								 NULL);
		
		AutoPtr<dng_memory_block> bufferData (host.Allocate (buffer.fRowStep));
		
		buffer.fData = bufferData->Buffer ();
		
		for (uint32 row = 0; row < cinfo.image_height; row++)
			{
			
			buffer.fArea.t = row;
			buffer.fArea.b = row + 1;
			
			image.Get (buffer);
			
			uint8 *sampArray [1];

			sampArray [0] = buffer.DirtyPixel_uint8 (row,
													 buffer.fArea.l,
													 0);

			jpeg_write_scanlines (&cinfo, sampArray, 1);
			
			}

		// Cleanup.
			
		jpeg_finish_compress (&cinfo);

		jpeg_destroy_compress (&cinfo);
			
		}
		
	catch (...)
		{
		
		jpeg_destroy_compress (&cinfo);
		
		throw;
		
		}
		
	AutoPtr<dng_memory_block> compressedData (stream.AsMemoryBlock (host.Allocator ()));
				   
	preview.SetCompressedData (compressedData);

	#else
	
	(void) host;
	(void) image;
	(void) preview;
	(void) quality;
	
	ThrowProgramError ("No JPEG encoder");
	
	#endif
		
	}
								
/*****************************************************************************/

void dng_image_writer::WriteTile (dng_host &host,
								  const dng_ifd &ifd,
								  dng_stream &stream,
								  const dng_image &image,
								  const dng_rect &tileArea,
								  uint32 fakeChannels,
								  AutoPtr<dng_memory_block> &compressedBuffer,
								  AutoPtr<dng_memory_block> &uncompressedBuffer,
								  AutoPtr<dng_memory_block> &subTileBlockBuffer,
								  AutoPtr<dng_memory_block> &tempBuffer,
								  bool usingMultipleThreads)
	{
	
	// Create pixel buffer to hold uncompressed tile.
	
	dng_pixel_buffer buffer (tileArea, 
							 0, 
							 ifd.fSamplesPerPixel,
							 image.PixelType (), 
							 pcInterleaved, 
							 uncompressedBuffer->Buffer ());
							 
	// Is this lossy compression?
	
	bool isLossyCompression = (ifd.fCompression == ccLossyJPEG) ||
							  (ifd.fCompression == ccJXL &&
									(!ifd.fJXLEncodeSettings ||
									  ifd.fJXLEncodeSettings->Distance () != 0.0f));
	
	// Get the uncompressed data.

	image.Get (buffer, isLossyCompression ? dng_image::edge_repeat
										  : dng_image::edge_zero);
	
	// Deal with sub-tile blocks.
	
	if (ifd.fSubTileBlockRows > 1)
		{
		
		ReorderSubTileBlocks (ifd,
							  buffer,
							  uncompressedBuffer,
							  subTileBlockBuffer);
		
		}
		
	// Floating point depth conversion.
	
	if (ifd.fSampleFormat [0] == sfFloatingPoint)
		{
		
		if (ifd.fBitsPerSample [0] == 16)
			{
			
			uint32 *srcPtr = (uint32 *) buffer.fData;
			uint16 *dstPtr = (uint16 *) buffer.fData;
			
			uint32 pixels = tileArea.W () * tileArea.H () * buffer.fPlanes;
			
			for (uint32 j = 0; j < pixels; j++)
				{
				
				dstPtr [j] = DNG_FloatToHalf (srcPtr [j]);
				
				}
				
			buffer.fPixelSize = 2;
			
			}
			
		if (ifd.fBitsPerSample [0] == 24)
			{
			
			uint32 *srcPtr = (uint32 *) buffer.fData;
			uint8  *dstPtr = (uint8	 *) buffer.fData;
			
			uint32 pixels = tileArea.W () * tileArea.H () * buffer.fPlanes;
			
			if (stream.BigEndian () || ifd.fPredictor == cpFloatingPoint   ||
									   ifd.fPredictor == cpFloatingPointX2 ||
									   ifd.fPredictor == cpFloatingPointX4)
				{
			
				for (uint32 j = 0; j < pixels; j++)
					{
					
					DNG_FloatToFP24 (srcPtr [j], dstPtr);
					
					dstPtr += 3;
					
					}
					
				}
				
			else
				{
			
				for (uint32 j = 0; j < pixels; j++)
					{
					
					uint8 output [3];
					
					DNG_FloatToFP24 (srcPtr [j], output);
					
					dstPtr [0] = output [2];
					dstPtr [1] = output [1];
					dstPtr [2] = output [0];
					
					dstPtr += 3;
					
					}
					
				}
				
			buffer.fPixelSize = 3;
			
			}
		
		}
	
	// Run predictor.
	
	EncodePredictor (host,
					 ifd,
					 buffer,
					 tempBuffer);
		
	// Adjust pixel buffer for fake channels.
	
	if (fakeChannels > 1)
		{
		
		buffer.fPlanes	*= fakeChannels;
		buffer.fColStep *= fakeChannels;
		
		buffer.fArea.r = buffer.fArea.l + (buffer.fArea.W () / fakeChannels);
		
		}
		
	// Compress (if required) and write out the data.
	
	WriteData (host,
			   ifd,
			   stream,
			   buffer,
			   compressedBuffer,
			   usingMultipleThreads);
			   
	}

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

dng_write_tiles_task::dng_write_tiles_task 
	(dng_image_writer &imageWriter,
	 dng_host &host,
	 const dng_ifd &ifd,
	 dng_basic_tag_set &basic,
	 dng_stream &stream,
	 const dng_image &image,
	 uint32 fakeChannels,
	 uint32 tilesDown,
	 uint32 tilesAcross,
	 uint32 compressedSize,
	 uint32 uncompressedSize,
	 bool needDigest)

	:	dng_area_task ("dng_write_tiles_task")

	,	fImageWriter	  (imageWriter)
	,	fHost			  (host)
	,	fIFD			  (ifd)
	,	fBasic			  (basic)
	,	fStream			  (stream)
	,	fImage			  (image)
	,	fFakeChannels	  (fakeChannels)
	,	fTilesDown		  (tilesDown)
	,	fTilesAcross	  (tilesAcross)
	,	fCompressedSize	  (compressedSize)
	,	fUncompressedSize (uncompressedSize)
	,	fNextTileIndex	  (0)
	,	fMutex			  ("dng_write_tiles_task")
	,	fCondition		  ()
	,	fTaskFailed		  (false)
	,	fWriteTileIndex	  (0)
	,	fNeedDigest		  (needDigest)

	{

	fMinTaskArea = 16 * 16;
	fUnitCell	 = dng_point (16, 16);
	fMaxTileSize = dng_point (16, 16);

	}

/*****************************************************************************/
	
void dng_write_tiles_task::Process (uint32 /* threadIndex */,
									const dng_rect & /* tile */,
									dng_abort_sniffer *sniffer)
	{

	try
		{

		AutoPtr<dng_memory_block> compressedBuffer;
		AutoPtr<dng_memory_block> uncompressedBuffer;
		AutoPtr<dng_memory_block> subTileBlockBuffer;
		AutoPtr<dng_memory_block> tempBuffer;

		if (fCompressedSize)
			{
			compressedBuffer.Reset (fHost.Allocate (fCompressedSize));
			}

		if (fUncompressedSize)
			{
			uncompressedBuffer.Reset (fHost.Allocate (fUncompressedSize));
			}

		if (fIFD.fSubTileBlockRows > 1 && fUncompressedSize)
			{
			subTileBlockBuffer.Reset (fHost.Allocate (fUncompressedSize));
			}

		while (true)
			{

			// Find tile index to compress.

			// Note: fNextTileIndex is atomic

			uint32 tileIndex = fNextTileIndex++;

			if (tileIndex >= fTilesDown * fTilesAcross)
				{
				return;
				}

			// Encode the tile. This may be done concurrently.

			uint32 tileByteCount = 0;

			dng_memory_stream tileStream (fHost.Allocator ());

			ProcessTask (tileIndex,
						 compressedBuffer,
						 uncompressedBuffer,
						 subTileBlockBuffer,
						 tempBuffer,
						 tileByteCount,
						 tileStream,
						 sniffer);

			// Compute tile digest if requested.

			dng_fingerprint tileDigest;

			if (fNeedDigest)
				{
				
				tileStream.SetReadPosition (0);
				
				dng_md5_printer_stream md5stream;
				
				tileStream.CopyToStream (md5stream, tileByteCount);
				
				tileDigest = md5stream.Result ();
				
				tileStream.SetReadPosition (0);

				}

			// Wait until it is our turn to write tile.

				{

				dng_lock_mutex lock (&fMutex);

				while (!fTaskFailed &&
					   fWriteTileIndex != tileIndex)
					{

					fCondition.Wait (fMutex);

					}

				// If the task failed in another thread, that thread already
				// threw an exception.

				if (fTaskFailed)
					return;

				}

			// Write the encoded tile to the output stream. This must be done
			// sequentially in ascending order of tileIndex (enforced by the
			// above 'wait').

			WriteTask (tileIndex,
					   tileByteCount,
					   tileStream,
					   sniffer);

			// Also update the overall digest if requested.

			if (fNeedDigest)
				{
				
				fOverallPrinter.Process (tileDigest.data,
										 uint32 (sizeof (tileDigest.data)));

				}

			// Let other threads know it is safe to write to stream.

				{

				dng_lock_mutex lock (&fMutex);

				// If the task failed in another thread, that thread already
				// threw an exception.

				if (fTaskFailed)
					return;

				fWriteTileIndex++;

				fCondition.Broadcast ();

				}

			}

		}

	catch (...)
		{

		// If first to fail, wake up any threads waiting on condition.

		bool needBroadcast = false;

			{

			dng_lock_mutex lock (&fMutex);

			needBroadcast = !fTaskFailed;
			fTaskFailed = true;

			}

		if (needBroadcast)
			fCondition.Broadcast ();

		throw;

		}

	}

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

void dng_write_tiles_task::ProcessTask 
	(uint32 tileIndex,
	 AutoPtr<dng_memory_block> &compressedBuffer,
	 AutoPtr<dng_memory_block> &uncompressedBuffer,
	 AutoPtr<dng_memory_block> &subTileBlockBuffer,
	 AutoPtr<dng_memory_block> &tempBuffer,
	 uint32 &tileByteCount, // output
	 dng_memory_stream &tileStream, // output
	 dng_abort_sniffer *sniffer)
	{

	// This routine may be executed concurrently.

	dng_abort_sniffer::SniffForAbort (sniffer);

	// Compress tile.
					
	uint32 rowIndex = tileIndex / fTilesAcross;
					
	uint32 colIndex = tileIndex - rowIndex * fTilesAcross;
					
	dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex);
										   
	tileStream.SetLittleEndian (fStream.LittleEndian ());
					
	dng_host host (&fHost.Allocator (),
				   sniffer);

	if (fHost.JXLEncodeSettings ())
		{
		host.SetJXLEncodeSettings (*fHost.JXLEncodeSettings ());
		}

	if (fHost.JXLColorSpaceInfo ())
		{
		host.SetJXLColorSpaceInfo (fHost.ShareJXLColorSpaceInfo ());
		}

	fImageWriter.WriteTile (host,
							fIFD,
							tileStream,
							fImage,
							tileArea,
							fFakeChannels,
							compressedBuffer,
							uncompressedBuffer,
							subTileBlockBuffer,
							tempBuffer,
							true);
											
	tileStream.Flush ();
											
	tileByteCount = (uint32) tileStream.Length ();
					
	tileStream.SetReadPosition (0);
					
	}

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

void dng_write_tiles_task::WriteTask (uint32 tileIndex,
									  uint32 tileByteCount,
									  dng_memory_stream &tileStream,
									  dng_abort_sniffer *sniffer)
	{

	// This task must be executed serially and in sequential (ascending) order
	// of tileIndex.

	dng_abort_sniffer::SniffForAbort (sniffer);

	// Remember this offset.

	uint64 tileOffset = fStream.Position ();

	fBasic.SetTileOffset (tileIndex, tileOffset);

	// Copy tile stream for tile into main stream.

	tileStream.CopyToStream (fStream, tileByteCount);

	// Update tile count.

	fBasic.SetTileByteCount (tileIndex, tileByteCount);

	// Keep the tiles on even byte offsets.

	if (tileByteCount & 1)
		{
		fStream.Put_uint8 (0);
		}

	}

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

const dng_fingerprint & dng_write_tiles_task::ResultDigest () const
	{
	
	return fOverallPrinter.Result ();
	
	}

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

void dng_image_writer::DoWriteTiles (dng_host &host,
									 const dng_ifd &ifd,
									 dng_basic_tag_set &basic,
									 dng_stream &stream,
									 const dng_image &image,
									 uint32 fakeChannels,
									 uint32 tilesDown,
									 uint32 tilesAcross,
									 uint32 compressedSize,
									 const dng_safe_uint32 &uncompressedSize,
									 dng_fingerprint *outDigest)
	{
	
	uint32 threadCount = Min_uint32 (tilesDown * tilesAcross,
									 host.PerformAreaTaskThreads ());
										 
	dng_write_tiles_task task (*this,
							   host,
							   ifd,
							   basic,
							   stream,
							   image,
							   fakeChannels,
							   tilesDown,
							   tilesAcross,
							   compressedSize,
							   uncompressedSize.Get (),
							   (outDigest != nullptr));
								  
	host.PerformAreaTask (task,
						  dng_rect (0, 0, 16, 16 * threadCount));

	if (outDigest)
		{
		
		*outDigest = task.ResultDigest ();

		#if qDNGValidate && 0
		printf ("write digest: %s\n",
				outDigest->ToUtf8HexString ().Get ());
		#endif

		}
	
	}

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

void dng_image_writer::WriteImage (dng_host &host,
								   const dng_ifd &ifd,
								   dng_basic_tag_set &basic,
								   dng_stream &stream,
								   const dng_image &image,
								   uint32 fakeChannels,
								   dng_fingerprint *outDigest)
	{

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

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

		// First, apply interleaving to a temporary image.

		AutoPtr<dng_image> tempImage (host.Make_dng_image (image.Bounds (),
														   image.Planes (),
														   image.PixelType ()));
		
		Interleave2D (host,
					  image,
					  *tempImage,
					  ifd.fRowInterleaveFactor,
					  ifd.fColumnInterleaveFactor,
					  true);
	
		// Then write the interleaved image.

		dng_ifd tempIFD (ifd);

		tempIFD.fRowInterleaveFactor    = 1;
		tempIFD.fColumnInterleaveFactor = 1;

		WriteImage (host,
					tempIFD,
					basic,
					stream,
					*tempImage,
					fakeChannels,
					outDigest);

		return;
		
		}

	#if qDNGValidate
	
	char message [256];
	
	AutoPtr<dng_timer> timer;
	
	if (ifd.fCompression == ccJXL && ifd.fJXLEncodeSettings.get ())
		{
		
		snprintf (message,
				  sizeof (message),
				  "JXL write %u by %u pixels, distance = %.2f, effort = %d",
				  image.Width (),
				  image.Height (),
				  ifd.fJXLEncodeSettings->Distance (),
				  ifd.fJXLEncodeSettings->Effort ());
				  
		timer.Reset (new dng_timer (message));
		
		}
	
	#endif

	// Compute basic information.

	dng_safe_uint32 bytesPerSample (TagTypeSize (image.PixelType ()));
	
	dng_safe_uint32 bytesPerPixel = bytesPerSample * ifd.fSamplesPerPixel;
	
	dng_safe_uint32 tileRowBytes = bytesPerPixel * ifd.fTileWidth;

	// If we can compute the number of bytes needed to store the
	// data, we can split the write for each tile into sub-tiles.
	
	uint32 subTileLength = ifd.fTileLength;
	
	if (ifd.TileByteCount (ifd.TileArea (0, 0)) != 0)
		{
		
		subTileLength = Pin_uint32 (ifd.fSubTileBlockRows,
									kImageBufferSize / tileRowBytes.Get (), 
									ifd.fTileLength);
					
		// Don't split sub-tiles across subTileBlocks.
		
		subTileLength = subTileLength / ifd.fSubTileBlockRows
									  * ifd.fSubTileBlockRows;
									
		}
		
	// Find size of uncompressed buffer.
	
	dng_safe_uint32 uncompressedSize = tileRowBytes * subTileLength;
	
	// Find size of compressed buffer, if required.
	
	uint32 compressedSize = CompressedBufferSize (ifd, uncompressedSize.Get ());
			
	// See if we can do this write using multiple threads.
	
	uint32 tilesAcross = ifd.TilesAcross ();
	uint32 tilesDown   = ifd.TilesDown	 ();
							   
	bool useMultipleThreads = (tilesDown * tilesAcross >= 2) &&
							  (host.PerformAreaTaskThreads () > 1) &&
							  (subTileLength == ifd.fTileLength) &&
							  (ifd.fCompression != ccUncompressed);
	
	if (useMultipleThreads)
		{

		DoWriteTiles (host,
					  ifd,
					  basic,
					  stream,
					  image,
					  fakeChannels,
					  tilesDown,
					  tilesAcross,
					  compressedSize,
					  uncompressedSize.Get (),
					  outDigest);

		}
		
	else
		{
									
		AutoPtr<dng_memory_block> compressedBuffer;
		AutoPtr<dng_memory_block> uncompressedBuffer;
		AutoPtr<dng_memory_block> subTileBlockBuffer;
		AutoPtr<dng_memory_block> tempBuffer;
		
		if (compressedSize)
			{
			compressedBuffer.Reset (host.Allocate (compressedSize));
			}
		
		if (uncompressedSize.Get ())
			{
			uncompressedBuffer.Reset (host.Allocate (uncompressedSize.Get ()));
			}
		
		if (ifd.fSubTileBlockRows > 1 && uncompressedSize.Get ())
			{
			subTileBlockBuffer.Reset (host.Allocate (uncompressedSize.Get ()));
			}

		dng_md5_printer overallPrinter;

		const bool needDigest = (outDigest != nullptr);
				
		// Write out each tile.
		
		uint32 tileIndex = 0;
		
		for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
			{
			
			for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
				{
				
				// Remember this offset.
				
				uint64 tileOffset = stream.Position ();
			
				basic.SetTileOffset (tileIndex, tileOffset);
				
				// Split tile into sub-tiles if possible.
				
				dng_rect tileArea = ifd.TileArea (rowIndex, colIndex);
				
				uint32 subTileCount = (tileArea.H () + subTileLength - 1) /
									  subTileLength;

				// If we need a digest, then write the tile to a local stream so
				// that we can compute a fingerprint on it before copying the
				// data to the main stream.

				dng_memory_stream tileStream (host.Allocator ());

				auto &subStream = needDigest ? tileStream : stream;

				for (uint32 subIndex = 0; subIndex < subTileCount; subIndex++)
					{
					
					host.SniffForAbort ();
				
					dng_rect subArea (tileArea);
					
					subArea.t = tileArea.t + subIndex * subTileLength;
					
					subArea.b = Min_int32 (subArea.t + subTileLength,
										   tileArea.b);
										   
					// Write the sub-tile.
					
					WriteTile (host,
							   ifd,
							   subStream,
							   image,
							   subArea,
							   fakeChannels,
							   compressedBuffer,
							   uncompressedBuffer,
							   subTileBlockBuffer,
							   tempBuffer,
							   useMultipleThreads);
							   
					}

				uint64 tileByteCount;

				if (needDigest)
					{

					// Get the tile byte count.

					tileStream.Flush ();

					tileByteCount = tileStream.Length ();

					// Compute the tile digest.

					tileStream.SetReadPosition (0);
					
					dng_md5_printer_stream md5stream;
					
					tileStream.CopyToStream (md5stream, tileByteCount);
					
					dng_fingerprint tileDigest = md5stream.Result ();

					// Update the overall digest.

					overallPrinter.Process (tileDigest.data,
											uint32 (sizeof (tileDigest.data)));

					// Copy the tile data to the main stream.
					
					tileStream.SetReadPosition (0);
					
					tileStream.CopyToStream (stream, tileByteCount);

					}

				else
					{
					
					// Update tile count.
					
					tileByteCount = stream.Position () - tileOffset;
					
					}
					
				basic.SetTileByteCount (tileIndex, tileByteCount);
				
				tileIndex++;
				
				// Keep the tiles on even byte offsets.
													 
				if (tileByteCount & 1)
					{
					stream.Put_uint8 (0);
					}
					
				} // tiles across (cols)

			} // tiles down (rows)

		// Optionally store the image digest.

		if (outDigest)
			{

			*outDigest = overallPrinter.Result ();

			#if qDNGValidate && 0
			printf ("single-thread write digest: %s\n",
					outDigest->ToUtf8HexString ().Get ());
			#endif

			}
			
		} // concurrent vs single-thread
		
	}

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

#if qDNGUseXMP

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

static void CopyString (const dng_xmp &oldXMP,
						dng_xmp &newXMP,
						const char *ns,
						const char *path,
						dng_string *exif = NULL)
	{
	
	dng_string s;
	
	if (oldXMP.GetString (ns, path, s))
		{
		
		if (s.NotEmpty ())
			{
			
			newXMP.SetString (ns, path, s);
			
			if (exif)
				{
				
				*exif = s;
				
				}
			
			}
			
		}
	
	}
								 
/*****************************************************************************/

static void CopyStringList (const dng_xmp &oldXMP,
							dng_xmp &newXMP,
							const char *ns,
							const char *path,
							bool isBag)
	{
	
	dng_string_list list;
	
	if (oldXMP.GetStringList (ns, path, list))
		{
		
		if (list.Count ())
			{
			
			newXMP.SetStringList (ns, path, list, isBag);
						
			}
			
		}
	
	}
								 
/*****************************************************************************/

static void CopyAltLangDefault (const dng_xmp &oldXMP,
								dng_xmp &newXMP,
								const char *ns,
								const char *path,
								dng_string *exif = NULL)
	{
	
	dng_string s;
	
	if (oldXMP.GetAltLangDefault (ns, path, s))
		{
		
		if (s.NotEmpty ())
			{
			
			newXMP.SetAltLangDefault (ns, path, s);
			
			if (exif)
				{
				
				*exif = s;
				
				}
			
			}
			
		}
	
	}
								 
/*****************************************************************************/

static void CopyStructField (const dng_xmp &oldXMP,
							 dng_xmp &newXMP,
							 const char *ns,
							 const char *path,
							 const char *field)
	{
	
	dng_string s;
	
	if (oldXMP.GetStructField (ns, path, ns, field, s))
		{
		
		if (s.NotEmpty ())
			{
			
			newXMP.SetStructField (ns, path, ns, field, s);
			
			}
			
		}
	
	}

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

static void CopyBoolean (const dng_xmp &oldXMP,
						 dng_xmp &newXMP,
						 const char *ns,
						 const char *path)
	{
	
	bool b;
	
	if (oldXMP.GetBoolean (ns, path, b))
		{
		
		newXMP.SetBoolean (ns, path, b);
						
		}
	
	}
	
/*****************************************************************************/

#endif	// qDNGUseXMP
								 
/*****************************************************************************/

void dng_image_writer::CleanUpMetadata (dng_host &host,
										dng_metadata &metadata,
										dng_metadata_subset metadataSubset,
										const char *dstMIME,
										const char *software)
	{
	
	#if qDNGUseXMP
	
	if (metadata.GetXMP () && metadata.GetExif ())
		{
		
		dng_xmp	 &newXMP  (*metadata.GetXMP	 ());
		dng_exif &newEXIF (*metadata.GetExif ());
		
		// Update software tag.
		
		if (software)
			{
	
			newEXIF.fSoftware.Set (software);
			
			newXMP.Set (XMP_NS_XAP,
						"CreatorTool",
						software);
			
			}
		
		#if qDNGXMPDocOps
		
		newXMP.DocOpsPrepareForSave (metadata.SourceMIME ().Get (),
									 dstMIME);
												  
		#else
		
		metadata.UpdateDateTimeToNow ();
		
		#endif
		
		// Update EXIF version to at least 2.3.1 so all the exif tags
		// can be written.
		
		if (!newEXIF.AtLeastVersion0231 ())
			{
		
			newEXIF.SetVersion0231 ();
			
			newXMP.Set (XMP_NS_EXIF, "ExifVersion", "0231");
			
			}
			
		// Require that XMP be the only source for saved GPS data.
		
			{
		
			dng_exif blankExif;
		
			newEXIF.CopyGPSFrom (blankExif);
			
			}
		
		// Resync EXIF, remove EXIF tags from XMP.
	
		newXMP.SyncExif (newEXIF,
						 metadata.GetOriginalExif (),
						 false,
						 true);
									  
		// Deal with ImageIngesterPro bug.	This program is adding lots of
		// empty metadata strings into the XMP, which is screwing up Adobe CS4.
		// We are saving a new file, so this is a chance to clean up this mess.
		
		newXMP.RemoveEmptyStringsAndArrays (XMP_NS_DC);
		newXMP.RemoveEmptyStringsAndArrays (XMP_NS_XAP);
		newXMP.RemoveEmptyStringsAndArrays (XMP_NS_PHOTOSHOP);
		newXMP.RemoveEmptyStringsAndArrays (XMP_NS_IPTC);
		newXMP.RemoveEmptyStringsAndArrays (XMP_NS_XAP_RIGHTS);
		newXMP.RemoveEmptyStringsAndArrays ("http://ns.iview-multimedia.com/mediapro/1.0/");

		// Process metadata subset.
  
		if (metadataSubset != dng_metadata_subset::kMask_All)
			{
			
			// Take a snapshot of the complete metadata.
			
			dng_xmp	 oldXMP	 (newXMP );
			dng_exif oldEXIF (newEXIF);
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Other))
				{
				
				// For these options, we start from nothing, and only fill in the
				// fields that we absolutely need.
					
				newXMP.RemoveProperties (NULL);
				
				newEXIF.SetEmpty ();
				
				metadata.ClearMakerNote ();

				// Exif version is always required.

				newEXIF.fExifVersion = oldEXIF.fExifVersion;
				
				#if qDNGXMPDocOps
				
				// Include basic DocOps fields, but not the full history.
				
				CopyString (oldXMP,
							newXMP,
							XMP_NS_MM,
							"OriginalDocumentID");
							
				CopyString (oldXMP,
							newXMP,
							XMP_NS_MM,
							"DocumentID");
				
				CopyString (oldXMP,
							newXMP,
							XMP_NS_MM,
							"InstanceID");
				
				CopyString (oldXMP,
							newXMP,
							XMP_NS_XAP,
							"MetadataDate");
				
				#endif

				// Restore HDR metadata.

				newXMP.DuplicateNameSpace (oldXMP, XMP_NS_HDR_META);
				newXMP.DuplicateNameSpace (oldXMP, XMP_NS_HDRGM);
				newXMP.DuplicateNameSpace (oldXMP, XMP_NS_APPLE_HDRGM);
					
				// Restore Camera Exif info.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Exif))
					{

					newEXIF = oldEXIF;
					
					// But don't retore the GPS part of the exif block.
					
					dng_exif blankExif;
					
					newEXIF.CopyGPSFrom (blankExif);
					
					// Restore the EXIF information in the XMP.
					
					newXMP.DuplicateNameSpace (oldXMP, XMP_NS_EXIF);
					newXMP.DuplicateNameSpace (oldXMP, XMP_NS_EXIFEX);
					newXMP.DuplicateNameSpace (oldXMP, XMP_NS_AUX);
					
					}

				// Restore EXIF date-related fields.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_ExifDate))
					{
					
					newEXIF.CopyDateFrom (oldEXIF);
					
					}

				// Restore location info.
					
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Location))
					{
					
					// Restore GPS location info.
					
					newEXIF.CopyGPSFrom (oldEXIF);
					
					// Restore XMP & IPTC location info.
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_PHOTOSHOP,
								"City");
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_PHOTOSHOP,
								"State");
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_PHOTOSHOP,
								"Country");
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_IPTC,
								"Location");
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_IPTC,
								"CountryCode");
								
					newXMP.DuplicateSubtree (oldXMP,
											 XMP_NS_IPTC_EXT,
											 "LocationCreated");
					
					newXMP.DuplicateSubtree (oldXMP,
											 XMP_NS_IPTC_EXT,
											 "LocationShown");
											
					}

				// Move Camera Raw settings namespace over.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_CameraRaw))
					{
					
					newXMP.DuplicateNameSpace (oldXMP, XMP_NS_CRS);
					
					}

				// Move copyright related fields over.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Copyright))
					{
					
					CopyAltLangDefault (oldXMP,
										newXMP,
										XMP_NS_DC,
										"rights",
										&newEXIF.fCopyright);
														
					CopyAltLangDefault (oldXMP,
										newXMP,
										XMP_NS_XAP_RIGHTS,
										"UsageTerms");
										
					CopyString (oldXMP,
								newXMP,
								XMP_NS_XAP_RIGHTS,
								"WebStatement");
								
					CopyBoolean (oldXMP,
								 newXMP,
								 XMP_NS_XAP_RIGHTS,
								 "Marked");
									 
					}

				// Move contact info fields over.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Contact))
					{
					
					// Note: Save for Web is not including the dc:creator list, but it
					// is part of the IPTC contract info metadata panel, so I
					// think it should be copied as part of the contact info.
					
					CopyStringList (oldXMP,
									newXMP,
									XMP_NS_DC,
									"creator",
									false);
									
					// The first string dc:creator list is mirrored to the
					// the exif artist tag, so copy that also.
									
					newEXIF.fArtist = oldEXIF.fArtist;
					
					// Copy other contact fields.
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_PHOTOSHOP,
								"AuthorsPosition");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiEmailWork");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiAdrExtadr");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiAdrCity");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiAdrRegion");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiAdrPcode");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiAdrCtry");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiTelWork");
								
					CopyStructField (oldXMP,
									 newXMP,
									 XMP_NS_IPTC,
									 "CreatorContactInfo",
									 "CiUrlWork");
									 
					}
					
				// Restore keywords.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Keywords))
					{
					
					CopyStringList (oldXMP,
									newXMP,
									XMP_NS_DC,
									"subject",
									true);
							
					CopyStringList (oldXMP,
									newXMP,
									XMP_NS_LR,
									"hierarchicalSubject",
									true);
							
					}
					
				// Restore rating.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Rating))
					{
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_XAP,
								"Rating");
								
					}
							
				// Restore label.
					
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Label))
					{
					
					CopyString (oldXMP,
								newXMP,
								XMP_NS_XAP,
								"Label");
								
					}
					
				// Restore caption.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Caption))
					{
					
					CopyAltLangDefault (oldXMP,
										newXMP,
										XMP_NS_DC,
										"description");

					newEXIF.fImageDescription = oldEXIF.fImageDescription;
					
					}
									
				// Restore title.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Title))
					{
					
					CopyAltLangDefault (oldXMP,
										newXMP,
										XMP_NS_DC,
										"title");

					}
								
				// Restore regions.
				
				if (metadataSubset.Includes (dng_metadata_subset::kMask_Regions))
					{
					
					newXMP.DuplicateNameSpace (oldXMP, XMP_NS_REGIONS);

					}
												
				}
				
			else
				{
				
				// These options start with complete metadata and remove
				// the metadata that we explicitly do not want to include.
				
				// Remove Camera Raw info.
				
				if (metadataSubset.Excludes (dng_metadata_subset::kMask_CameraRaw))
					{
					
					newXMP.RemoveProperties (XMP_NS_CRS);
					newXMP.RemoveProperties (XMP_NS_CRSS);
					newXMP.RemoveProperties (XMP_NS_CRX);

					// Remove DocOps history, since it contains processing history.

					newXMP.Remove (XMP_NS_MM, "History");

					}

				// Remove Camera Exif info.
				
				if (metadataSubset.Excludes (dng_metadata_subset::kMask_Exif))
					{

					// This removes most of the EXIF info, so just copy the fields
					// we are not deleting.
					
					newEXIF.SetEmpty ();
					
					newEXIF.fImageDescription  = oldEXIF.fImageDescription;		// Note: Differs from SFW
					newEXIF.fSoftware		   = oldEXIF.fSoftware;
					newEXIF.fArtist			   = oldEXIF.fArtist;
					newEXIF.fCopyright		   = oldEXIF.fCopyright;
					newEXIF.fCopyright2		   = oldEXIF.fCopyright2;
					newEXIF.fDateTime		   = oldEXIF.fDateTime;
					newEXIF.fDateTimeOriginal  = oldEXIF.fDateTimeOriginal;
					newEXIF.fDateTimeDigitized = oldEXIF.fDateTimeDigitized;
					newEXIF.fExifVersion	   = oldEXIF.fExifVersion;
					newEXIF.fImageUniqueID	   = oldEXIF.fImageUniqueID;
					
					newEXIF.CopyGPSFrom (oldEXIF);
					
					// Remove exif info from XMP.
					
					newXMP.RemoveProperties (XMP_NS_EXIF);
					newXMP.RemoveProperties (XMP_NS_EXIFEX);
					newXMP.RemoveProperties (XMP_NS_AUX);
					
					// Remove Panorama info.

					newXMP.RemoveProperties (XMP_NS_PANO);
					newXMP.RemoveProperties (XMP_NS_GPANO);

					// MakerNote contains camera info.
					
					metadata.ClearMakerNote ();
				
					// Remove DocOps history, since it contains the original
					// camera format.

					newXMP.Remove (XMP_NS_MM, "History");

					}
					
				}
				
			// Remove keywords.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Keywords))
				{
				
				newXMP.Remove (XMP_NS_DC, "subject");
				
				newXMP.Remove (XMP_NS_LR, "hierarchicalSubject");
							
				}

			// Remove location info.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Location))
				{
				
				// Remove GPS fields.
				
				dng_exif blankExif;
				
				newEXIF.CopyGPSFrom (blankExif);
				
				// Remove MakerNote just in case, because we don't know
				// all of what is in it.
				
				metadata.ClearMakerNote ();
				
				// Remove XMP & IPTC location fields.
				
				newXMP.Remove (XMP_NS_PHOTOSHOP, "City");
				newXMP.Remove (XMP_NS_PHOTOSHOP, "State");
				newXMP.Remove (XMP_NS_PHOTOSHOP, "Country");
				newXMP.Remove (XMP_NS_IPTC, "Location");
				newXMP.Remove (XMP_NS_IPTC, "CountryCode");
				newXMP.Remove (XMP_NS_IPTC_EXT, "LocationCreated");
				newXMP.Remove (XMP_NS_IPTC_EXT, "LocationShown");
				
				}
				
			// Remove contact info.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Contact))
				{
				
				newXMP.Remove (XMP_NS_DC, "creator");
				
				newEXIF.fArtist.Clear ();
				
				newXMP.Remove (XMP_NS_PHOTOSHOP, "AuthorsPosition");
							
				newXMP.Remove (XMP_NS_IPTC, "CreatorContactInfo");
				
				// Leave XMP_NS_DC:Title.
												
				}
				
			// Remove copyright.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Copyright))
				{
				
				newXMP.Remove (XMP_NS_DC, "rights");
				
				newXMP.Remove (XMP_NS_XAP_RIGHTS, "UsageTerms");
				newXMP.Remove (XMP_NS_XAP_RIGHTS, "WebStatement");
				newXMP.Remove (XMP_NS_XAP_RIGHTS, "Marked");
				
				newEXIF.fCopyright .Clear ();
				newEXIF.fCopyright2.Clear ();

				}
					
			// Remove rating.
				
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Rating))
				{
				
				newXMP.Remove (XMP_NS_XAP, "Rating");
							
				}
						
			// Remove label.
				
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Label))
				{
				
				newXMP.Remove (XMP_NS_XAP, "Label");
							
				}
				
			// Remove caption.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Caption))
				{
				
				newXMP.Remove (XMP_NS_DC, "description");
				
				newEXIF.fImageDescription.Clear ();
				
				}
								
			// Remove title.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Title))
				{
				
				newXMP.Remove (XMP_NS_DC, "title");
				
				}
									
			// Remove regions.
			
			if (metadataSubset.Excludes (dng_metadata_subset::kMask_Regions))
				{
				
				newXMP.RemoveProperties (XMP_NS_REGIONS);

				}
										
			}
									  
		// Rebuild the legacy IPTC block, if needed.
		
		bool isTIFF = (strcmp (dstMIME, "image/tiff") == 0);
		bool isDNG	= (strcmp (dstMIME, "image/dng" ) == 0);

		if (!isDNG)
			{
		
			metadata.RebuildIPTC (host.Allocator (),
								  isTIFF);
								  
			}
			
		else
			{
			
			metadata.ClearIPTC ();
			
			}
	
		// Clear format related XMP.
									  
		newXMP.ClearOrientation ();
		
		newXMP.ClearImageInfo ();
		
		newXMP.RemoveProperties (XMP_NS_DNG);
		
		// Clear default camera raw settings if not writing to DNG.
		
		if (!isDNG)
			{
			
			newXMP.RemoveProperties (XMP_NS_CRD);
			newXMP.RemoveProperties (XMP_NS_CRLCP);
			
			}
		
		// All the formats we care about already keep the IPTC digest
		// elsewhere, do we don't need to write it to the XMP.
		
		newXMP.ClearIPTCDigest ();
		
		// Make sure that sidecar specific tags never get written to files.
		
		newXMP.Remove (XMP_NS_PHOTOSHOP, "SidecarForExtension");
		newXMP.Remove (XMP_NS_PHOTOSHOP, "EmbeddedXMPDigest");
		
		}
	
	#endif	// qDNGUseXMP

	}
	

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

void dng_image_writer::UpdateExifColorSpaceTag (dng_metadata &metadata,
												const void *profileData,
												const uint32 profileSize)
	{
	
	if (!metadata.GetExif ())
		{
		return;
		}

	dng_exif &exif = *metadata.GetExif ();

	if (profileData && profileSize)
		{

		uint32 tagValue = 0xFFFF;

		// Is the color profile sRGB IEC61966-2.1?

		uint32 sRGB_size	   = 0;
		const uint8 *sRGB_data = 0;

		if (dng_space_sRGB::Get ().ICCProfile (sRGB_size,
											   sRGB_data))
			{

			if ((sRGB_size == profileSize) &&
				!memcmp (profileData, 
						 (const void *) sRGB_data, 
						 (size_t) sRGB_size))
				{

				// Yes. 
					
				tagValue = 1;

				}
				
			}

		exif.fColorSpace = tagValue;
		
		}

	}

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

void dng_image_writer::WriteTIFF (dng_host &host,
								  dng_stream &stream,
								  const dng_image &image,
								  uint32 photometricInterpretation,
								  uint32 compression,
								  const dng_metadata *metadata,
								  const dng_color_space *space,
								  const dng_resolution *resolution,
								  const dng_jpeg_preview *thumbnail,
								  const dng_memory_block *imageResources,
								  dng_metadata_subset metadataSubset,
								  bool hasTransparency,
								  bool allowBigTIFF,
								  const dng_image *gainMapImage,
								  const bool useHalfFloat)
	{
	
	const void *profileData = NULL;
	uint32 profileSize = 0;
	
	const uint8 *data = NULL;
	uint32 size = 0;
	
	if (space && space->ICCProfile (size, data))
		{
		
		profileData = data;
		profileSize = size;
		
		}
		
	WriteTIFFWithProfile (host,
						  stream,
						  image,
						  photometricInterpretation,
						  compression,
						  metadata,
						  profileData,
						  profileSize,
						  resolution,
						  thumbnail,
						  imageResources,
						  metadataSubset,
						  hasTransparency,
						  allowBigTIFF,
						  gainMapImage,
						  useHalfFloat);
	
	}

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

static void SetupIFDfromCompressedImage (dng_ifd &info,
										 const dng_lossy_compressed_image &compressedImage)
	{
	
	info.fCompression = compressedImage.fCompressionCode;
	
	info.fTileWidth  = compressedImage.fTileSize.h;
	info.fTileLength = compressedImage.fTileSize.v;
	
	info.fUsesStrips =  compressedImage.fUsesStrips;
	info.fUsesTiles  = !compressedImage.fUsesStrips;
	
	for (uint32 j = 1; j < info.fSamplesPerPixel; j++)
		{
		info.fBitsPerSample [j] = compressedImage.fBitsPerSample;
		}
	
	info.fRowInterleaveFactor    = compressedImage.fRowInterleaveFactor;
	info.fColumnInterleaveFactor = compressedImage.fColumnInterleaveFactor;
	
	info.fJXLDistance    = compressedImage.JXLDistance    ();
	info.fJXLEffort      = compressedImage.JXLEffort      ();
	info.fJXLDecodeSpeed = compressedImage.JXLDecodeSpeed ();
	
	}

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

static dng_ifd * MakeGainMapIFD (const dng_image &image,
								 const dng_lossy_compressed_image *lossyCompressed,
								 const uint32 compression)
	{
	
	DNG_REQUIRE (image.Planes () == 1 ||
				 image.Planes () == 3,
				 "Invalid image plane count for MakeGainMapIFD");

	AutoPtr<dng_ifd> result (new dng_ifd);

	auto &ifd = *result;

	ifd.fNewSubFileType = sfGainMap;
	
	ifd.fImageWidth	 = image.Bounds ().W ();
	ifd.fImageLength = image.Bounds ().H ();
	
	ifd.fSamplesPerPixel = image.Planes ();
	
	ifd.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
	
	for (uint32 j = 1; j < ifd.fSamplesPerPixel; j++)
		{
		ifd.fBitsPerSample [j] = ifd.fBitsPerSample [0];
		}
		
	ifd.fPhotometricInterpretation = piGainMap;
	
	ifd.fExtraSamplesCount = 0;
	
	if (lossyCompressed)
		{
		
		SetupIFDfromCompressedImage (ifd,
									 *lossyCompressed);
		
		}
		
	else
		{

		ifd.fCompression = compression;
		
		if (ifd.fCompression == ccUncompressed)
			{
			
			ifd.SetSingleStrip ();
			
			}
			
		else
			{

			ifd.FindStripSize (128 * 1024);

			// Deflate/ZIP.
			
			if (ifd.fCompression == ccDeflate)
				{
				
				if (image.PixelType () == ttFloat)
					{
					ifd.fPredictor = cpFloatingPoint;
					}
					
				else
					{
					ifd.fPredictor = cpHorizontalDifference;
					}
				
				}
			
			}
			
		}

	return result.Release ();

	}

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

void dng_image_writer::WriteTIFFWithProfile (dng_host &host,
											 dng_stream &stream,
											 const dng_image &image,
											 uint32 photometricInterpretation,
											 uint32 compression,
											 const dng_metadata *constMetadata,
											 const void *profileData,
											 uint32 profileSize,
											 const dng_resolution *resolution,
											 const dng_jpeg_preview *thumbnail,
											 const dng_memory_block *imageResources,
											 dng_metadata_subset metadataSubset,
											 bool hasTransparency,
											 bool allowBigTIFF,
											 const dng_image *gainMapImage,
											 const bool useHalfFloat)
	{
	
	// Force writing all TIFF files in BigTIFF format.
	
	#define qForceWriteBigTIFF 0
	
	// Force writing stress test BigTIFF files with all file offsets 64-bit.
	
	#define qStressTestBigTIFF 0

	#if qStressTestBigTIFF
	const uint64 kStressTestBytes = 0x100000000ULL;
	#endif
	
	AutoPtr<dng_metadata> metadata;
	
	if (constMetadata)
		{
		
		metadata.Reset (constMetadata->Clone (host.Allocator ()));
		
		CleanUpMetadata (host, 
						 *metadata,
						 metadataSubset,
						 "image/tiff");

		UpdateExifColorSpaceTag (*metadata, 
								 profileData, 
								 profileSize);
		
		}
	
	dng_ifd ifd;
	
	ifd.fNewSubFileType = sfMainImage;
	
	ifd.fImageWidth	 = image.Bounds ().W ();
	ifd.fImageLength = image.Bounds ().H ();
	
	ifd.fSamplesPerPixel = image.Planes ();

	if ((image.PixelType () == ttFloat) && useHalfFloat)
		ifd.fBitsPerSample [0] = 16;

	else
		ifd.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
	
	for (uint32 j = 1; j < ifd.fSamplesPerPixel; j++)
		{
		ifd.fBitsPerSample [j] = ifd.fBitsPerSample [0];
		}
		
	ifd.fPhotometricInterpretation = photometricInterpretation;
	
	ifd.fCompression = compression;
	
	if (ifd.fCompression == ccUncompressed)
		{
		
		ifd.SetSingleStrip ();
		
		}
		
	else
		{

		if (ifd.fCompression == ccJXL)
			{
			
			ifd.FindTileSize (2048 * 2048);
			
			}

		else
			{
		
			ifd.FindStripSize (128 * 1024);

			}
		
		if (ifd.fCompression != ccJPEG &&
			ifd.fCompression != ccJXL)
			{
			
			if (image.PixelType () == ttFloat)
				{
				ifd.fPredictor = cpFloatingPoint;
				}
				
			else
				{
				ifd.fPredictor = cpHorizontalDifference;
				}
			
			}
		
		}

	uint32 extraSamples = 0;
	
	switch (photometricInterpretation)
		{
		
		case piBlackIsZero:
			{
			extraSamples = image.Planes () - 1;
			break;
			}
			
		case piRGB:
		case piCIELab:
		case piICCLab:
			{
			extraSamples = image.Planes () - 3;
			break;
			}
			
		case piCMYK:
			{
			extraSamples = image.Planes () - 4;
			break;
			}
			
		default:
			break;
			
		}
		
	ifd.fExtraSamplesCount = extraSamples;
	
	if (hasTransparency && extraSamples)
		{
		ifd.fExtraSamples [0] = esAssociatedAlpha;
		}
	
	if (image.PixelType () == ttFloat)
		{
		
		for (uint32 j = 0; j < ifd.fSamplesPerPixel; j++)
			{
			ifd.fSampleFormat [j] = sfFloatingPoint;
			}
			
		}

	dng_tiff_directory mainIFD;
	
	dng_basic_tag_set basic (mainIFD, ifd);

	// Gain map.
	
	dng_tiff_directory gainMapIFD;

	AutoPtr<dng_basic_tag_set> gainMapTagSet;
	
	AutoPtr<dng_ifd> gainMapImageIFD;

	const bool hasGainMap = (gainMapImage != nullptr);

	if (hasGainMap)
		{
		
		uint32 gainMapCompression = compression;
		
		if (gainMapImage->PixelType () == ttFloat &&
			gainMapCompression != ccDeflate)
			{
			gainMapCompression = ccUncompressed;
			}
		
		gainMapImageIFD.Reset (MakeGainMapIFD (*gainMapImage,
											   nullptr,
											   gainMapCompression));

		gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD,
													*gainMapImageIFD));
		
		}

	// Resolution.
	
	dng_resolution res;
	
	if (resolution)
		{
		res = *resolution;
		}
	
	tag_urational tagXResolution (tcXResolution, res.fXResolution);
	tag_urational tagYResolution (tcYResolution, res.fYResolution);
	
	tag_uint16 tagResolutionUnit (tcResolutionUnit, res.fResolutionUnit);
	
	if (resolution)
		{
		mainIFD.Add (&tagXResolution   );
		mainIFD.Add (&tagYResolution   );
		mainIFD.Add (&tagResolutionUnit);
		}

	// ICC Profile.
	
	tag_icc_profile iccProfileTag (profileData, profileSize);
	
	if (iccProfileTag.Count ())
		{
		mainIFD.Add (&iccProfileTag);
		}
		
	// XMP metadata.
	
	#if qDNGUseXMP
	
	tag_xmp tagXMP (metadata.Get () ? metadata->GetXMP () : NULL);
	
	if (tagXMP.Count ())
		{
		mainIFD.Add (&tagXMP);
		}
		
	#endif
	
	// IPTC metadata.
	
	tag_iptc tagIPTC (metadata.Get () ? metadata->IPTCData	 () : NULL,
					  metadata.Get () ? metadata->IPTCLength () : 0);
		
	if (tagIPTC.Count ())
		{
		mainIFD.Add (&tagIPTC);
		}
		
	// Adobe data (thumbnail and IPTC digest)
	
	AutoPtr<dng_memory_block> adobeData (BuildAdobeData (host,
														 metadata.Get (),
														 thumbnail,
														 imageResources));
														 
	tag_uint8_ptr tagAdobe (tcAdobeData,
							adobeData->Buffer_uint8 (),
							adobeData->LogicalSize ());
								 
	if (tagAdobe.Count ())
		{
		mainIFD.Add (&tagAdobe);
		}
		
	// Exif metadata.
	
	const dng_exif emptyExif;

	const dng_exif *metaExifPtr = metadata.Get () ? metadata->GetExif () : nullptr;

	const dng_exif &exifRef = metaExifPtr ? *metaExifPtr : emptyExif;
	
	exif_tag_set exifSet (mainIFD,
						  exifRef,
						  metadata.Get () ? metadata->IsMakerNoteSafe () : false,
						  metadata.Get () ? metadata->MakerNoteData	  () : NULL,
						  metadata.Get () ? metadata->MakerNoteLength () : 0,
						  false);

	// SubIFDs.

	uint32 subFileCount = 0;

	if (hasGainMap)
		subFileCount++;

	// Add a link to the subIFDs.

	tag_big_uints tagSubFile (tcSubIFDs,
							  subFileCount);
							   
	if (subFileCount)
		mainIFD.Add (&tagSubFile);

	// Find header size as 32-bit TIFF
	
	uint64 headerSize32 = 8 +
						  mainIFD.Size () +
						  exifSet.Size () +
						  (hasGainMap ? gainMapIFD.Size () : 0);
						  
	// Find header size as 64-bit TIFF
	
	mainIFD.SetBigTIFF (true);
	exifSet.SetBigTIFF (true);

	if (hasGainMap)
		gainMapIFD.SetBigTIFF (true);

	uint64 headerSize64 = 16 +
						  mainIFD.Size () +
						  exifSet.Size () +
						  (hasGainMap ? gainMapIFD.Size () : 0);
						  
	// Find maximum size of non-header size.
	
	uint64 maxNonHeaderSize =
		(ifd.MaxImageDataByteCount () +
		 (hasGainMap ? gainMapImageIFD->MaxImageDataByteCount () : 0));
	
	// Maximum target size of 32-bit TIFF files that allows some slop for
	// future metadata edits.
	
	const uint64 kMaxTargetNonBigTIFFSize = 0xFFFFFFFF - 16 * 1024 * 1024;
	
	// Don't reserve extra space if we know we are not going to use it.
	
	if (headerSize32 + maxNonHeaderSize < kMaxTargetNonBigTIFFSize)
		{
		allowBigTIFF = false;
		}
						  
	#if qForceWriteBigTIFF || qStressTestBigTIFF

	allowBigTIFF = true;
	
	#if qCRStressTestBigTIFF
	headerSize64 += kStressTestBytes;
	#endif
	
	#endif
	
	// Reserve enough space for 64-bit header.
	
	uint64 headerSize = allowBigTIFF ? headerSize64
									 : headerSize32;
	
	stream.SetWritePosition (headerSize);
						  
	// Write the main image data.
	
	WriteImage (host,
				ifd,
				basic,
				stream,
				image);

	// Write the gain map image.

	if (gainMapImage)
		{
		
		WriteImage (host,
					*gainMapImageIFD,
					*gainMapTagSet,
					stream,
					*gainMapImage);
		
		}
				
	// If we are not allowed to write BigTIFF, and the file
	// is over 4GB limit, throw an error.
	
	if (!allowBigTIFF)
		{
		
		if (stream.Position () > 0xFFFFFFFF)
			{
			ThrowImageTooBigTIFF ();
			}
		
		}
				
	// Trim the file to this length.
	
	stream.SetLength (stream.Position ());
	
	// Decide if we should write 32 or 64 bit header.
	
	bool isBigTIFF = allowBigTIFF && (stream.Length () > kMaxTargetNonBigTIFFSize);
	
	#if qForceWriteBigTIFF || qStressTestBigTIFF
	isBigTIFF = allowBigTIFF;
	#endif
		
	mainIFD.SetBigTIFF (isBigTIFF);
	exifSet.SetBigTIFF (isBigTIFF);

	if (hasGainMap)
		gainMapIFD.SetBigTIFF (isBigTIFF);
	
	// Write header.
	
	stream.SetWritePosition (0);
	
	stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
	
	if (isBigTIFF)
		{
		
		stream.Put_uint16 (magicBigTIFF);
		
		stream.Put_uint16 (8);
		stream.Put_uint16 (0);
		
		#if qStressTestBigTIFF
		stream.Put_uint64 (16 + kStressTestBytes);
		stream.PutZeros (kStressTestBytes);
		#else
		stream.Put_uint64 (16);
		#endif
			
		}
		
	else
		{
	
		stream.Put_uint16 (magicTIFF);
		
		stream.Put_uint32 (8);
		
		}

	// Write IFDs.
					
	uint64 offsetMainIFD = stream.Position ();
	
	uint64 offsetExifData = offsetMainIFD + mainIFD.Size ();
	
	uint64 offsetGainMapIFD = offsetExifData + exifSet.Size ();
	
	exifSet.Locate (offsetExifData);

	if (hasGainMap)
		tagSubFile.Set (0, offsetGainMapIFD);
	
	mainIFD.Put (stream);
	
	exifSet.Put (stream);

	if (hasGainMap)
		gainMapIFD.Put (stream);
	
	if (headerSize > stream.Position ())
		{
		
		stream.PutZeros (headerSize - stream.Position ());
		
		}
	
	stream.Flush ();
	
	}
							   
/*****************************************************************************/

void dng_image_writer::WriteDNG (dng_host &host,
								 dng_stream &stream,
								 dng_negative &negative,
								 const dng_preview_list *previewList,
								 uint32 maxBackwardVersion,
								 bool uncompressed,
								 bool allowBigTIFF,
								 const dng_image *gainMapImage,
								 const dng_lossy_compressed_image *gainMapLossyCompressed)
	{
	
	WriteDNGWithMetadata (host,
						  stream,
						  negative,
						  negative.Metadata (),
						  previewList,
						  maxBackwardVersion,
						  uncompressed,
						  allowBigTIFF,
						  gainMapImage,
						  gainMapLossyCompressed);
	
	}
	
/*****************************************************************************/

static bool HasDNGOpcode_1_6 (const dng_negative &negative,
							  bool includeOptional)
	{

	const uint32 version = dngVersion_1_6_0_0;

	return (negative.OpcodeList1 ().MinVersion (includeOptional) >= version ||
			negative.OpcodeList2 ().MinVersion (includeOptional) >= version ||
			negative.OpcodeList3 ().MinVersion (includeOptional) >= version);
	
	}

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

#if qDNGDebug

class dng_verify_size_estimate
	{
	
	private:
	
		dng_stream &fStream;
	
		uint64 fStart;
		
		uint64 fEstimate;
		
		const char *fMessage;
		
	public:
	
		dng_verify_size_estimate (dng_stream &stream,
								  uint64 estimate,
								  const char *message)
								  
			:	fStream	  (stream)
			,	fStart	  (stream.Position ())
			,	fEstimate (estimate)
			,	fMessage  (message)
			
			{
			
			}
			
		~dng_verify_size_estimate ()
			{
			
			uint64 actual = fStream.Position () - fStart;
			
			char message [2048];
			
			snprintf (message,
					  2048,
					  "%s: actual size = %llu, estimated size = %llu",
					  fMessage,
					  (unsigned long long) actual,
					  (unsigned long long) fEstimate);
					 
			DNG_ASSERT (actual <= fEstimate, message);
			
			#if 0
			fprintf (stderr, "%s\n", message);
			#endif
			
			}
		
	};
	
#define DNG_VERIFY_SIZE_ESTIMATE(stream,estimate,message) \
	dng_verify_size_estimate verify (stream, estimate, message);

#else

#define DNG_VERIFY_SIZE_ESTIMATE(stream,estimate,message) \
	(void) estimate;

#endif

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

static void SetEncodeSettingsForIFD (const dng_image &image,
									 const bool uncompressed,
									 const uint32 maxBackwardVersion,
									 dng_ifd &info,
									 const dng_lossy_compressed_image *compressedImage = nullptr)
	{
	
	const bool isFloat = (image.PixelType () == ttFloat);
	
	// Set sample format for floating-point data.
			
	if (isFloat)
		{

		for (uint32 i = 0; i < info.fSamplesPerPixel; i++)
			info.fSampleFormat [i] = sfFloatingPoint;

		}
			
	// If we already have a lossy compressed version of image, use that.
	
	if (compressedImage)
		{
		
		SetupIFDfromCompressedImage (info, *compressedImage);
				
		return;
		
		}

	// Pick compression method.

	if (uncompressed)
		{
		
		info.fCompression = ccUncompressed;

		}

	else if (isFloat)
		{
		
		info.fCompression = ccDeflate;
		
		}

	else if (image.PixelType () == ttLong)
			 
		{

		if (maxBackwardVersion >= dngVersion_1_4_0_0)
			info.fCompression = ccDeflate;

		else
			info.fCompression = ccUncompressed;
		
		}

	else
		{

		// Lossless JPEG.
		
		info.fCompression = ccJPEG;
		
		}

	// Uncompressed.

	if (info.fCompression == ccUncompressed)
		{
		
		info.fPredictor = cpNullPredictor;
			
		info.SetSingleStrip ();
		
		}

	// Deflate.
		
	else if (info.fCompression == ccDeflate)
		{
		
		info.fPredictor = isFloat ? cpFloatingPoint
								  : cpHorizontalDifference;
				
		info.FindTileSize (512 * 1024);

		}

	// Everything else, including lossy and lossless JPEG.
		
	else
		{
		
		info.FindTileSize (128 * 1024);

		}

	}

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

void dng_image_writer::WriteDNGWithMetadata (dng_host &host,
											 dng_stream &stream,
											 const dng_negative &negative,
											 const dng_metadata &constMetadata,
											 const dng_preview_list *previewList,
											 const uint32 maxBackwardVersion,
											 const bool uncompressed,
											 bool allowBigTIFF,
											 const dng_image *gainMapImage,
											 const dng_lossy_compressed_image *gainMapLossyCompressed)
	{
	
	// Force writing all DNG files in 64-bit format.
	
	#define qForceWriteBigDNG 0
	
	// Force writing stress test 64-bit DNG files with all file offsets 64-bit.
	
	#define qStressTestBigDNG 0

	#if qStressTestBigDNG
	const uint64 kStressTestBytes = 0x100000000ULL;
	#endif
	
	// Clean up metadata per MWG recommendations.
	
	AutoPtr<dng_metadata> metadata (constMetadata.Clone (host.Allocator ()));

	CleanUpMetadata (host, 
					 *metadata,
					 kMetadataSubset_All,
					 "image/dng");

	// Was the original file lossy compressed with JPEG or some other method?
	
	const dng_lossy_compressed_image *rawLossyCompressedImage =
		negative.RawLossyCompressedImage ();
		
	// If not, then if there was a raw lossy compressed digest,
	// we need to compute the image digest.
	
	if (!rawLossyCompressedImage && negative.RawLossyCompressedImageDigest ().IsValid ())
		{
		
		negative.ClearRawLossyCompressedImageDigest ();
			
		negative.ClearRawImageDigest ();
		
		}
		
	// See if we need to discard the raw lossy compressed image.
	
	if (rawLossyCompressedImage)
		{
		
		if (uncompressed ||
			maxBackwardVersion < MinBackwardVersionForCompression
								 (rawLossyCompressedImage->fCompressionCode) ||
			(rawLossyCompressedImage->fColumnInterleaveFactor != 1 &&
			 maxBackwardVersion < dngVersion_1_7_1_0))
			{
			
			rawLossyCompressedImage = nullptr;
			
			negative.ClearRawLossyCompressedImageDigest ();
			
			negative.ClearRawImageDigest ();

			}
		
		}
		
	// Is this a floating point image that we are saving?
	
	const bool isFloatingPoint =
		((negative.RawImage ().PixelType () == ttFloat) ||
		 (negative.RawImage ().PixelType () == ttHalfFloat));
	
	// Should we save a compressed 32-bit integer file?
	
	const bool isCompressed32BitInteger = (negative.RawImage ().PixelType () == ttLong) &&
										  (maxBackwardVersion >= dngVersion_1_4_0_0) &&
										  (!uncompressed);
	
	// Get a copy of the mosaic info.
	
	dng_mosaic_info mosaicInfo;
	
	if (negative.GetMosaicInfo ())
		{
		mosaicInfo = *(negative.GetMosaicInfo ());
		}
		
	// Figure out the compression to use. Most of the time this is lossless
	// JPEG.

	uint32 compression = uncompressed ? ccUncompressed : ccJPEG;

	if (!uncompressed)
		{

		// If we have some lossy compressed data, just use that.
		
		if (rawLossyCompressedImage)
			compression = rawLossyCompressedImage->fCompressionCode;

		// For floating point, we only support ZIP.
	
		else if (isFloatingPoint)
			compression = ccDeflate;

		// For 32-bit integer images, we only support ZIP and uncompressed.

		else if (negative.RawImage ().PixelType () == ttLong)
			compression = isCompressed32BitInteger ? ccDeflate : ccUncompressed;

		// Fall back to Lossless JPEG.
		
		else
			compression = ccJPEG;
		
		}

	// Are we saving the original size tags?
	
	bool saveOriginalDefaultFinalSize	  = false;
	bool saveOriginalBestQualityFinalSize = false;
	bool saveOriginalDefaultCropSize	  = false;
	
		{
		
		// See if we are saving a proxy image.
		
		dng_point defaultFinalSize (negative.DefaultFinalHeight (),
									negative.DefaultFinalWidth	());
									
		saveOriginalDefaultFinalSize = (negative.OriginalDefaultFinalSize () !=
										defaultFinalSize);
		
		if (saveOriginalDefaultFinalSize)
			{
			
			// If the save OriginalDefaultFinalSize tag, this changes the defaults
			// for the OriginalBestQualityFinalSize and OriginalDefaultCropSize tags.
			
			saveOriginalBestQualityFinalSize = (negative.OriginalBestQualityFinalSize () != 
												negative.OriginalDefaultFinalSize ());
												
			saveOriginalDefaultCropSize = (negative.OriginalDefaultCropSizeV () !=
										   dng_urational (negative.OriginalDefaultFinalSize ().v, 1)) ||
										  (negative.OriginalDefaultCropSizeH () !=
										   dng_urational (negative.OriginalDefaultFinalSize ().h, 1));

			}
			
		else
			{
			
			// Else these two tags default to the normal non-proxy size image values.
			
			dng_point bestQualityFinalSize (negative.BestQualityFinalHeight (),
											negative.BestQualityFinalWidth	());
											
			saveOriginalBestQualityFinalSize = (negative.OriginalBestQualityFinalSize () != 
												bestQualityFinalSize);
												
			saveOriginalDefaultCropSize = (negative.OriginalDefaultCropSizeV () !=
										   negative.DefaultCropSizeV ()) ||
										  (negative.OriginalDefaultCropSizeH () !=
										   negative.DefaultCropSizeH ());
			
			}
		
		}
		
	// Does this image have a transparency mask?
	
	bool hasTransparencyMask = (negative.RawTransparencyMask () != NULL);
	
	const dng_lossy_compressed_image *rawLossyCompressedTransparencyMask =
		negative.RawLossyCompressedTransparencyMask ();
		
	if (rawLossyCompressedTransparencyMask &&
		(maxBackwardVersion <
		 MinBackwardVersionForCompression (rawLossyCompressedTransparencyMask->fCompressionCode)))
		{
		rawLossyCompressedTransparencyMask = nullptr;
		}
	
	// Does the image have depth mask?
	
	bool hasDepthMap = (negative.RawDepthMap () != NULL);
	
	// Should we save the enhanced stage 3 image?
	
	bool hasEnhancedImage = (&negative.RawImage () != negative.Stage3Image ()) &&
							negative.EnhanceParams ().NotEmpty ();
	
	// Profile and other color related tags. Do this up front so that we can
	// determine how the set of profiles influences the DNG version and
	// backward version tags.
	
	AutoPtr<profile_tag_set> profileSet;
	
	AutoPtr<color_tag_set> colorSet;
	
	std::vector<uint32> extraProfileIndex;
	
	dng_camera_profile mainProfile;

	bool hasProfileWith_1_6_Features = false;
	bool hasProfileWith_1_7_Features = false;
		
	// Create the main IFD.
										 
	dng_tiff_directory mainIFD;
	
	if (!negative.IsMonochrome ())
		{
		
		(void) negative.GetProfileToEmbed (constMetadata, mainProfile);
		
		if (mainProfile.Uses_1_6_Features ())
			{
			hasProfileWith_1_6_Features = true;
			}
		
		if (mainProfile.Uses_1_7_Features ())
			{
			hasProfileWith_1_7_Features = true;
			}
		
		profileSet.Reset (new profile_tag_set (host,
											   mainIFD,
											   mainProfile));
		
		colorSet.Reset (new color_tag_set (mainIFD,
										   negative));
										   
		// Build list of profile indices to include in extra profiles tag.
										   
		uint32 profileCount = negative.ProfileCount ();
		
		for (uint32 index = 0; index < profileCount; index++)
			{
			
			const dng_camera_profile &profile (negative.ProfileByIndex (index));
			
			if (profile.WasReadFromDNG () &&
			   !profile.Name ().Matches (mainProfile.Name ().Get ()))
				{
				
				extraProfileIndex.push_back (index);

				if (profile.Uses_1_6_Features ())
					{
					hasProfileWith_1_6_Features = true;
					}

				if (profile.Uses_1_7_Features ())
					{
					hasProfileWith_1_7_Features = true;
					}

				}
				
			}
										   
		}

	// Does the negative have a valid ImageSequenceInfo tag?

	const bool hasImageSequenceInfo = metadata->ImageSequenceInfo ().IsValid ();
		
	// Does the negative has a valid ImageStats tag?

	const bool hasImageStats =
		((metadata->ImageStats ().TagCount () > 0) &&
		 metadata->ImageStats ().IsValidForPlaneCount (negative.ColorChannels ()));

	// Does the negative have a valid ProfileGainTableMap2 tag?
	
	const bool hasProfileGainTableMap2 =
		(negative.HasProfileGainTableMap () &&
		 negative.ProfileGainTableMap ().RequiresVersion2 ());

	// Does the negative use the HDR output-referred colorimetric reference?

	const bool hasHDRColorimetricRef =
		(negative.ColorimetricReference () == crOutputReferredHDR);
	
	// Embedded big table tags.
	
	big_table_tag_set bigTableTagSet (host,
									  mainIFD,
									  metadata->BigTableDictionary (),
									  metadata->BigTableGroupIndex ());

	// Gain map.
	
	dng_tiff_directory gainMapIFD;

	AutoPtr<dng_basic_tag_set> gainMapTagSet;
	
	AutoPtr<dng_ifd> gainMapImageIFD;

	const bool hasGainMap = (gainMapImage != nullptr);

	if (hasGainMap)
		{

		uint32 gainMapCompression = (gainMapImage->PixelType () == ttFloat)
									? ccDeflate
									: ccJPEG;
											
		gainMapImageIFD.Reset (MakeGainMapIFD (*gainMapImage,
											   gainMapLossyCompressed,
											   gainMapCompression));

		gainMapTagSet.Reset (new dng_basic_tag_set (gainMapIFD,
													*gainMapImageIFD));
		
		}
		
	// Figure out what main version to use.
	
	uint32 dngVersion 		  = dngVersion_1_4_0_0;
	uint32 dngBackwardVersion = dngVersion_1_1_0_0;

	// Check DNG 1.7 feature usage.
	
	bool has_1_7_Features = false;

	if (compression == ccJXL)
		{
		
		has_1_7_Features = true;
		
		}
		
	else if (hasTransparencyMask &&
			 rawLossyCompressedTransparencyMask &&
			 rawLossyCompressedTransparencyMask->fCompressionCode == ccJXL)
		{
		
		has_1_7_Features = true;
		
		}
		
	else if (hasEnhancedImage &&
			 negative.EnhancedLossyCompressedImage () &&
			 negative.EnhancedLossyCompressedImage ()->fCompressionCode == ccJXL)
		{
		
		has_1_7_Features = true;
		
		}
	
	else if (hasDepthMap &&
			 negative.RawLossyCompressedDepthMap () &&
			 negative.RawLossyCompressedDepthMap ()->fCompressionCode == ccJXL)
		{
		
		has_1_7_Features = true;
		
		}
		
	else if (negative.HasSemanticMask ())
		{
		
		for (uint32 j = 0; j < negative.NumSemanticMasks (); j++)
			{

			const auto &rawMask = negative.RawSemanticMask (j);
			
			if (rawMask.fLossyCompressed.get () &&
				rawMask.fLossyCompressed->fCompressionCode == ccJXL)
				{
				has_1_7_Features = true;
				}
				
			}
			
		}
		
	if (previewList && !has_1_7_Features)
		{
		
		for (uint32 j = 0; j < previewList->Count (); j++)
			{
			
			dng_tiff_directory tempDirectory;
			
			AutoPtr<dng_basic_tag_set> tempBasic (previewList->Preview (j).AddTagSet (host,
																					  tempDirectory));
			
			if (tempBasic.Get () && tempBasic->Compression () == ccJXL)
				{
				has_1_7_Features = true;
				break;
				}
				
			}

		}
	
	// Check other DNG 1.7 feature usage.

	if (has_1_7_Features            ||
		hasImageStats			    ||
		hasImageSequenceInfo	    ||
		hasHDRColorimetricRef       ||
		hasProfileGainTableMap2     ||
		hasProfileWith_1_7_Features ||
		hasGainMap                  )
		{
		
		dngVersion = Max_uint32 (dngVersion, dngVersion_1_7_0_0);
		
		}
		
	// Check DNG 1.6 feature usage.
		
	const bool hasRGBTables =
		(negative.ProfileCount () > 0 &&
		 negative.ProfileByIndex (0).HasMaskedRGBTables ());
	
	if (HasDNGOpcode_1_6 (negative, false) || // don't include optional opcodes
		hasProfileWith_1_6_Features ||
		negative.HasSemanticMask () ||
		negative.HasProfileGainTableMap () ||
		hasRGBTables)
		{
		
		dngVersion = Max_uint32 (dngVersion, dngVersion_1_6_0_0);
		
		}

	// Check DNG 1.5 feature usage.

	if (hasDepthMap || hasEnhancedImage)
		{
		
		dngVersion = Max_uint32 (dngVersion, dngVersion_1_5_0_0);
		
		}
		
	#if defined(qTestRowInterleave) || defined(qTestSubTileBlockRows) || defined(qTestSubTileBlockCols)
	dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_2_0_0);
	#endif
	
	#if defined(qTestColumnInterleave)
	dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_7_1_0);
	#endif

	dngBackwardVersion = Max_uint32 (dngBackwardVersion,
									 negative.OpcodeList1 ().MinVersion (false));

	dngBackwardVersion = Max_uint32 (dngBackwardVersion,
									 negative.OpcodeList2 ().MinVersion (false));

	dngBackwardVersion = Max_uint32 (dngBackwardVersion,
									 negative.OpcodeList3 ().MinVersion (false));
									 
	if (negative.GetMosaicInfo () &&
		negative.GetMosaicInfo ()->fCFALayout >= 6)
		{
		dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_3_0_0);
		}
		
	if (isFloatingPoint || hasTransparencyMask || isCompressed32BitInteger)
		{
		dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_4_0_0);
		}

	if (rawLossyCompressedImage)
		{
		
		dngBackwardVersion =
			Max_uint32 (dngBackwardVersion,
						MinBackwardVersionForCompression
							(rawLossyCompressedImage->fCompressionCode));
							
		if (rawLossyCompressedImage->fColumnInterleaveFactor != 1)
			{
			dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_7_1_0);
			}

		}

	if (rawLossyCompressedTransparencyMask)
		{
		dngBackwardVersion =
			Max_uint32 (dngBackwardVersion,
						MinBackwardVersionForCompression
							(rawLossyCompressedTransparencyMask->fCompressionCode));
		}

	// The dngVersion must be at least the dngBackwardVersion.
	
	dngVersion = Max_uint32 (dngVersion, dngBackwardVersion);
	
	// Our computed dngBackwardVersion should never be higher than the maxBackwardVersion.
	
	DNG_REQUIRE (dngBackwardVersion <= maxBackwardVersion,
				 "dngBackwardVersion too high");
		
	// Find best thumbnail from preview list, if any.

	const dng_preview *thumbnail = NULL;
	
	if (previewList)
		{
		
		uint32 thumbArea = 0;
		
		for (uint32 j = 0; j < previewList->Count (); j++)
			{
			
			const dng_preview &preview (previewList->Preview (j));
			
			if ((preview.NewSubFileType () == sfPreviewImage) &&
				(preview.PhotometricInterpretation () == piBlackIsZero ||
				 preview.PhotometricInterpretation () == piRGB         ||
				 preview.PhotometricInterpretation () == piYCbCr))
				{
				
				uint32 thisArea = preview.ImageWidth  () *
								  preview.ImageLength ();
								  
				if (!thumbnail || thisArea < thumbArea)
					{
					
					thumbnail = &preview;
					
					thumbArea = thisArea;
					
					}
			
				}
								
			}
		
		}
		
	// Create the IFD for the raw data. If there is no thumnail, this is
	// just a reference the main IFD.  Otherwise allocate a new one.
	
	AutoPtr<dng_tiff_directory> rawIFD_IfNotMain;
	
	if (thumbnail)
		{
		rawIFD_IfNotMain.Reset (new dng_tiff_directory);
		}

	dng_tiff_directory &rawIFD (thumbnail ? *rawIFD_IfNotMain : mainIFD);
	
	// Include DNG version tags.
	
	uint8 dngVersionData [4];
	
	dngVersionData [0] = (uint8) (dngVersion >> 24);
	dngVersionData [1] = (uint8) (dngVersion >> 16);
	dngVersionData [2] = (uint8) (dngVersion >>	 8);
	dngVersionData [3] = (uint8) (dngVersion	  );
	
	tag_uint8_ptr tagDNGVersion (tcDNGVersion, dngVersionData, 4);
	
	mainIFD.Add (&tagDNGVersion);
	
	uint8 dngBackwardVersionData [4];

	dngBackwardVersionData [0] = (uint8) (dngBackwardVersion >> 24);
	dngBackwardVersionData [1] = (uint8) (dngBackwardVersion >> 16);
	dngBackwardVersionData [2] = (uint8) (dngBackwardVersion >>	 8);
	dngBackwardVersionData [3] = (uint8) (dngBackwardVersion	  );
	
	tag_uint8_ptr tagDNGBackwardVersion (tcDNGBackwardVersion, dngBackwardVersionData, 4);
	
	mainIFD.Add (&tagDNGBackwardVersion);
	
	// The main IFD contains the thumbnail, if there is a thumbnail.
								
	AutoPtr<dng_basic_tag_set> thmBasic;
	
	if (thumbnail)
		{
		thmBasic.Reset (thumbnail->AddTagSet (host, mainIFD));
		}
						  
	// Get the raw image we are writing.

	const dng_image &rawImage (negative.RawImage ());
	
	// Create a dng_ifd record for the raw image.
	
	dng_ifd info;
	
	info.fImageWidth  = rawImage.Width	();
	info.fImageLength = rawImage.Height ();
	
	info.fSamplesPerPixel = rawImage.Planes ();
	
	info.fPhotometricInterpretation = mosaicInfo.IsColorFilterArray () ? piCFA
																	   : piLinearRaw;
			
	info.fCompression = compression;

	if (isFloatingPoint && (compression == ccDeflate))
		{
		
		info.fPredictor = cpFloatingPoint;
		
		if (mosaicInfo.IsColorFilterArray ())
			{
			
			if (mosaicInfo.fCFAPatternSize.h == 2)
				{
				info.fPredictor = cpFloatingPointX2;
				}
				
			else if (mosaicInfo.fCFAPatternSize.h == 4)
				{
				info.fPredictor = cpFloatingPointX4;
				}
				
			}
			
		} 
		
	if (isCompressed32BitInteger)
		{
		
		info.fPredictor = cpHorizontalDifference;
		
		if (mosaicInfo.IsColorFilterArray ())
			{
			
			if (mosaicInfo.fCFAPatternSize.h == 2)
				{
				info.fPredictor = cpHorizontalDifferenceX2;
				}
				
			else if (mosaicInfo.fCFAPatternSize.h == 4)
				{
				info.fPredictor = cpHorizontalDifferenceX4;
				}
				
			}
			
		}
	
	uint32 rawPixelType = rawImage.PixelType ();
	
	if (rawPixelType == ttShort)
		{
		
		// See if we are using a linearization table with <= 256 entries, in which
		// case the useful data will all fit within 8-bits.
		
		const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
	
		if (rangeInfo)
			{

			if (rangeInfo->fLinearizationTable.Get ())
				{
				
				uint32 entries = rangeInfo->fLinearizationTable->LogicalSize () >> 1;
				
				if (entries <= 256)
					{
					
					rawPixelType = ttByte;
					
					}
												
				}
				
			}

		}
	
	switch (rawPixelType)
		{
		
		case ttByte:
			{
			info.fBitsPerSample [0] = 8;
			break;
			}
		
		case ttShort:
			{
			info.fBitsPerSample [0] = 16;
			break;
			}
			
		case ttLong:
			{
			info.fBitsPerSample [0] = 32;
			break;
			}
			
		case ttFloat:
			{
			
			if (negative.RawFloatBitDepth () == 16)
				{
				info.fBitsPerSample [0] = 16;
				}
				
			else if (negative.RawFloatBitDepth () == 24)
				{
				info.fBitsPerSample [0] = 24;
				}
				
			else
				{
				info.fBitsPerSample [0] = 32;
				}

			for (uint32 j = 0; j < info.fSamplesPerPixel; j++)
				{
				info.fSampleFormat [j] = sfFloatingPoint;
				}

			break;
			
			}
			
		default:
			{
			ThrowProgramError ();
			}
			
		}
	
	// For lossless JPEG compression, we often lie about the
	// actual channel count to get the predictors to work across
	// same color mosaic pixels.
	
	uint32 fakeChannels = 1;
	
	if (info.fCompression == ccJPEG)
		{
		
		if (mosaicInfo.IsColorFilterArray ())
			{
			
			if (mosaicInfo.fCFAPatternSize.h == 4)
				{
				fakeChannels = 4;
				}
				
			else if (mosaicInfo.fCFAPatternSize.h == 2)
				{
				fakeChannels = 2;
				}
			
			// However, lossless JEPG is limited to four channels,
			// so compromise might be required.
			
			while (fakeChannels * info.fSamplesPerPixel > 4 &&
				   fakeChannels > 1)
				{
				
				fakeChannels >>= 1;
				
				}
			
			}
	
		}
		
	// Figure out tile sizes.
	
	if (rawLossyCompressedImage)
		{

		SetupIFDfromCompressedImage (info, *rawLossyCompressedImage);
				
		}
	
	else if (info.fCompression == ccJPEG)
		{
		
		info.FindTileSize (128 * 1024);
		
		}
		
	else if (info.fCompression == ccDeflate)
		{
		
		info.FindTileSize (512 * 1024);
		
		}
		
	else if (info.fCompression == ccJXL)
		{
		
		ThrowProgramError ("No JXL compressed image");
		
		}

	else if (info.fCompression == ccLossyJPEG)
		{
		
		ThrowProgramError ("No JPEG compressed image");
				
		}
		
	// Don't use tiles for uncompressed images.
		
	else
		{
		
		info.SetSingleStrip ();
		
		}
		
	#ifdef qTestRowInterleave
	info.fRowInterleaveFactor = qTestRowInterleave;
	fakeChannels = 1;
	#endif
			
	#ifdef qTestColumnInterleave
	info.fColumnInterleaveFactor = qTestColumnInterleave;
	fakeChannels = 1;
	#endif
			
	#if defined(qTestSubTileBlockRows) && defined(qTestSubTileBlockCols)
	
	info.fSubTileBlockRows = qTestSubTileBlockRows;
	info.fSubTileBlockCols = qTestSubTileBlockCols;
	
	if (fakeChannels == 2)
		fakeChannels = 4;
	
	#endif
	
	// Basic information.
	
	dng_basic_tag_set rawBasic (rawIFD, info);
	
	// TODO(erichan): For lossy compressed images, perhaps call a virtual
	// method to add special tags if needed.

	// JPEG tables, if any.
	
	tag_data_ptr tagJPEGTables (tcJPEGTables,
								ttUndefined,
								0,
								NULL);
								
	if (rawLossyCompressedImage && rawLossyCompressedImage->JPEGTables ())
		{
		
		tagJPEGTables.SetData (rawLossyCompressedImage->JPEGTables ()->Buffer ());
		
		tagJPEGTables.SetCount (rawLossyCompressedImage->JPEGTables ()->LogicalSize ());
		
		rawIFD.Add (&tagJPEGTables);
		
		}
						  
	// DefaultScale tag.

	dng_urational defaultScaleData [2];
	
	defaultScaleData [0] = negative.RawDefaultScaleH ().IsValid () ?
						   negative.RawDefaultScaleH () :
						   negative.DefaultScaleH ();
						   
	defaultScaleData [1] = negative.RawDefaultScaleV ().IsValid () ?
						   negative.RawDefaultScaleV () :
						   negative.DefaultScaleV ();
													
	tag_urational_ptr tagDefaultScale (tcDefaultScale,
									   defaultScaleData,
									   2);

	rawIFD.Add (&tagDefaultScale);
	
	// Best quality scale tag.
	
	tag_urational tagBestQualityScale (tcBestQualityScale,
									   negative.RawBestQualityScale ().IsValid () ?
									   negative.RawBestQualityScale () :
									   negative.BestQualityScale ());
									  
	rawIFD.Add (&tagBestQualityScale);
	
	// DefaultCropOrigin tag.

	dng_urational defaultCropOriginData [2];

	defaultCropOriginData [0] = negative.RawDefaultCropOriginH ().IsValid () ?
								negative.RawDefaultCropOriginH () :
								negative.DefaultCropOriginH ();
								
	defaultCropOriginData [1] = negative.RawDefaultCropOriginV ().IsValid () ?
								negative.RawDefaultCropOriginV () :
								negative.DefaultCropOriginV ();

	tag_urational_ptr tagDefaultCropOrigin (tcDefaultCropOrigin,
											defaultCropOriginData,
											2);

	rawIFD.Add (&tagDefaultCropOrigin);

	// DefaultCropSize tag.

	dng_urational defaultCropSizeData [2];

	defaultCropSizeData [0] = negative.RawDefaultCropSizeH ().IsValid () ?
							  negative.RawDefaultCropSizeH () :
							  negative.DefaultCropSizeH ();
							  
	defaultCropSizeData [1] = negative.RawDefaultCropSizeV ().IsValid () ?
							  negative.RawDefaultCropSizeV () :
							  negative.DefaultCropSizeV ();

	tag_urational_ptr tagDefaultCropSize (tcDefaultCropSize,
										  defaultCropSizeData,
										  2);

	rawIFD.Add (&tagDefaultCropSize);

	// DefaultUserCrop tag.

	dng_urational defaultUserCropData [4];

	defaultUserCropData [0] = negative.DefaultUserCropT ();
	defaultUserCropData [1] = negative.DefaultUserCropL ();
	defaultUserCropData [2] = negative.DefaultUserCropB ();
	defaultUserCropData [3] = negative.DefaultUserCropR ();

	tag_urational_ptr tagDefaultUserCrop (tcDefaultUserCrop,
										  defaultUserCropData,
										  4);

	if (negative.HasDefaultUserCrop ())
		{

		rawIFD.Add (&tagDefaultUserCrop);

		}
	
	// Range mapping tag set.
	
	range_tag_set rangeSet (rawIFD, negative);
						  
	// Mosaic pattern information.
	
	mosaic_tag_set mosaicSet (rawIFD, mosaicInfo);
			
	// Chroma blur radius.
	
	tag_urational tagChromaBlurRadius (tcChromaBlurRadius,
									   negative.ChromaBlurRadius ());
									  
	if (negative.ChromaBlurRadius ().IsValid ())
		{
		
		rawIFD.Add (&tagChromaBlurRadius);
		
		}
	
	// Anti-alias filter strength.
	
	tag_urational tagAntiAliasStrength (tcAntiAliasStrength,
										negative.AntiAliasStrength ());
									  
	if (negative.AntiAliasStrength ().IsValid ())
		{
		
		rawIFD.Add (&tagAntiAliasStrength);
		
		}
		
	// Extra camera profiles tag.
	
	uint32 extraProfileCount = (uint32) extraProfileIndex.size ();
	
	tag_big_uints extraProfileTag (tcExtraCameraProfiles,
								   extraProfileCount);
									
	if (extraProfileCount)
		{
		
		mainIFD.Add (&extraProfileTag);

		}
			
	// Other tags.
	
	tag_uint16 tagOrientation (tcOrientation,
							   (uint16) negative.ComputeOrientation (constMetadata).GetTIFF ());
							   
	mainIFD.Add (&tagOrientation);

	tag_srational tagBaselineExposure (tcBaselineExposure,
									   negative.BaselineExposureR ());
										  
	mainIFD.Add (&tagBaselineExposure);

	tag_urational tagBaselineNoise (tcBaselineNoise,
									negative.BaselineNoiseR ());
										  
	mainIFD.Add (&tagBaselineNoise);
	
	dng_urational rawNoiseReductionApplied = hasEnhancedImage ?
											 negative.RawNoiseReductionApplied () :
											 negative.NoiseReductionApplied ();

	tag_urational tagNoiseReductionAppliedMainIFD (tcNoiseReductionApplied,
												   rawNoiseReductionApplied);
											
	tag_urational tagNoiseReductionAppliedRawIFD (tcNoiseReductionApplied,
												  rawNoiseReductionApplied);
		
	if (rawNoiseReductionApplied.IsValid ())
		{
		
		rawIFD.Add (&tagNoiseReductionAppliedRawIFD);

		// Kludge: DNG spec says that the NoiseReductionApplied tag should be
		// in the Raw IFD, not main IFD. However, we also write a copy of this tag to
		// main IFD to deal with legacy DNG readers that try to read the tag
		// from the main IFD.

		if ((&rawIFD) != (&mainIFD))
			{
			
			mainIFD.Add (&tagNoiseReductionAppliedMainIFD);
	  
			}
	
		}
  
	dng_noise_profile rawNoiseProfile = hasEnhancedImage ?
										negative.RawNoiseProfile () :
										negative.NoiseProfile	 ();

	tag_dng_noise_profile tagNoiseProfileMainIFD (rawNoiseProfile);
	tag_dng_noise_profile tagNoiseProfileRawIFD	 (rawNoiseProfile);
		
	if (rawNoiseProfile.IsValidForNegative (negative))
		{

		rawIFD.Add (&tagNoiseProfileRawIFD);

		// Kludge: DNG spec says that NoiseProfile tag should be in the Raw
		// IFD, not main IFD. However, we also write a copy of this tag to
		// main IFD to deal with legacy DNG readers that try to read the tag
		// from the main IFD.

		if ((&rawIFD) != (&mainIFD))
			{

			mainIFD.Add (&tagNoiseProfileMainIFD);
			
			}
		
		}

	tag_urational tagBaselineSharpness (tcBaselineSharpness,
										negative.RawBaselineSharpness ().IsValid () && hasEnhancedImage ?
										negative.RawBaselineSharpness () :
										negative.BaselineSharpnessR ());
										  
	mainIFD.Add (&tagBaselineSharpness);

	tag_string tagUniqueName (tcUniqueCameraModel,
							  negative.ModelName (),
							  true);
								
	mainIFD.Add (&tagUniqueName);
	
	tag_string tagLocalName (tcLocalizedCameraModel,
							 negative.LocalName (),
							 false);
						   
	if (negative.LocalName ().NotEmpty ())
		{
		
		mainIFD.Add (&tagLocalName);
	
		}
	
	tag_urational tagShadowScale (tcShadowScale,
								  negative.ShadowScaleR ());
										  
	mainIFD.Add (&tagShadowScale);
	
	tag_uint16 tagColorimetricReference (tcColorimetricReference,
										 (uint16) negative.ColorimetricReference ());
										 
	if (negative.IsOutputReferred ())
		{
		
		mainIFD.Add (&tagColorimetricReference);
		
		}
		
	const bool useNewDigest = (maxBackwardVersion >= dngVersion_1_4_0_0);

	dng_fingerprint mainImageRawImageDigest;
	
	if (rawLossyCompressedImage)
		{
		
		negative.FindRawLossyCompressedImageDigest (host);
		
		mainImageRawImageDigest = negative.RawLossyCompressedImageDigest ();
		
		}
		
	else if (rawLossyCompressedTransparencyMask)
		{
		
		// For the edge case of a lossy compressed transparency mask, and a lossless
		// raw image, the raw image digest will not safely round trip, so don't write it.
		
		}
		
	else
		{
		
		if (useNewDigest)
			{
			negative.FindNewRawImageDigest (host);
			mainImageRawImageDigest = negative.NewRawImageDigest ();
			}
		
		else
			{
			negative.FindRawImageDigest (host);
			mainImageRawImageDigest = negative.RawImageDigest ();
			}
		
		}
	
	tag_uint8_ptr tagRawImageDigest (useNewDigest ? tcNewRawImageDigest : tcRawImageDigest,
									 mainImageRawImageDigest.data,
									 16);
									 
	if (mainImageRawImageDigest.IsValid ())
		{

		mainIFD.Add (&tagRawImageDigest);
		
		}
	
	negative.FindRawDataUniqueID (host);

	// Make a local copy of the raw data unique ID.

	const auto rawDataUniqueID = negative.RawDataUniqueID ();
	
	tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID,
									  rawDataUniqueID.data,
									  16);
									  
	if (rawDataUniqueID.IsValid ())
		{
							   
		mainIFD.Add (&tagRawDataUniqueID);
		
		}
	
	tag_string tagOriginalRawFileName (tcOriginalRawFileName,
									   negative.OriginalRawFileName (),
									   false);
						   
	if (negative.HasOriginalRawFileName ())
		{
		
		mainIFD.Add (&tagOriginalRawFileName);
	
		}
		
	negative.FindOriginalRawFileDigest ();
		
	tag_data_ptr tagOriginalRawFileData (tcOriginalRawFileData,
										 ttUndefined,
										 negative.OriginalRawFileDataLength (),
										 negative.OriginalRawFileData		());
										 
	tag_uint8_ptr tagOriginalRawFileDigest (tcOriginalRawFileDigest,
											negative.OriginalRawFileDigest ().data,
											16);
										 
	if (negative.OriginalRawFileData ())
		{
		
		mainIFD.Add (&tagOriginalRawFileData);
		
		mainIFD.Add (&tagOriginalRawFileDigest);
	
		}

	// XMP metadata.

	#if qDNGUseXMP
		
	tag_xmp tagXMP (metadata->GetXMP ());
	
	if (tagXMP.Count ())
		{
		
		mainIFD.Add (&tagXMP);
		
		}
		
	#endif
	
	// Exif tags.
	
	exif_tag_set exifSet (mainIFD,
						  *metadata->GetExif (),
						  metadata->IsMakerNoteSafe (),
						  metadata->MakerNoteData	(),
						  metadata->MakerNoteLength (),
						  true);
						
	// Private data.
	
	tag_uint8_ptr tagPrivateData (tcDNGPrivateData,
								  negative.PrivateData (),
								  negative.PrivateLength ());
						   
	if (negative.PrivateLength ())
		{
		
		mainIFD.Add (&tagPrivateData);
		
		}
		
	// Proxy size tags.
	
	uint32 originalDefaultFinalSizeData [2];
	
	originalDefaultFinalSizeData [0] = negative.OriginalDefaultFinalSize ().h;
	originalDefaultFinalSizeData [1] = negative.OriginalDefaultFinalSize ().v;
	
	tag_uint32_ptr tagOriginalDefaultFinalSize (tcOriginalDefaultFinalSize,
												originalDefaultFinalSizeData,
												2);
	
	if (saveOriginalDefaultFinalSize)
		{
		
		mainIFD.Add (&tagOriginalDefaultFinalSize);
		
		}
		
	uint32 originalBestQualityFinalSizeData [2];
	
	originalBestQualityFinalSizeData [0] = negative.OriginalBestQualityFinalSize ().h;
	originalBestQualityFinalSizeData [1] = negative.OriginalBestQualityFinalSize ().v;
	
	tag_uint32_ptr tagOriginalBestQualityFinalSize (tcOriginalBestQualityFinalSize,
													originalBestQualityFinalSizeData,
													2);
	
	if (saveOriginalBestQualityFinalSize)
		{
		
		mainIFD.Add (&tagOriginalBestQualityFinalSize);
		
		}
		
	dng_urational originalDefaultCropSizeData [2];
	
	originalDefaultCropSizeData [0] = negative.OriginalDefaultCropSizeH ();
	originalDefaultCropSizeData [1] = negative.OriginalDefaultCropSizeV ();
	
	tag_urational_ptr tagOriginalDefaultCropSize (tcOriginalDefaultCropSize,
												  originalDefaultCropSizeData,
												  2);
	
	if (saveOriginalDefaultCropSize)
		{
		
		mainIFD.Add (&tagOriginalDefaultCropSize);
		
		}
		
	// Opcode list 1.
	
	AutoPtr<dng_memory_block> opcodeList1Data (negative.OpcodeList1 ().Spool (host));
	
	tag_data_ptr tagOpcodeList1 (tcOpcodeList1,
								 ttUndefined,
								 opcodeList1Data.Get () ? opcodeList1Data->LogicalSize () : 0,
								 opcodeList1Data.Get () ? opcodeList1Data->Buffer	   () : NULL);
								 
	if (opcodeList1Data.Get ())
		{
		
		rawIFD.Add (&tagOpcodeList1);
		
		}
		
	// Opcode list 2.
	
	AutoPtr<dng_memory_block> opcodeList2Data (negative.OpcodeList2 ().Spool (host));
	
	tag_data_ptr tagOpcodeList2 (tcOpcodeList2,
								 ttUndefined,
								 opcodeList2Data.Get () ? opcodeList2Data->LogicalSize () : 0,
								 opcodeList2Data.Get () ? opcodeList2Data->Buffer	   () : NULL);
								 
	if (opcodeList2Data.Get ())
		{
		
		rawIFD.Add (&tagOpcodeList2);
		
		}
		
	// Opcode list 3.
	
	AutoPtr<dng_memory_block> opcodeList3Data (negative.OpcodeList3 ().Spool (host));
	
	tag_data_ptr tagOpcodeList3 (tcOpcodeList3,
								 ttUndefined,
								 opcodeList3Data.Get () ? opcodeList3Data->LogicalSize () : 0,
								 opcodeList3Data.Get () ? opcodeList3Data->Buffer	   () : NULL);
								 
	if (opcodeList3Data.Get ())
		{
		
		rawIFD.Add (&tagOpcodeList3);
		
		}

	// ProfileGainTableMap.

	AutoPtr<dng_memory_block> profileGainTableMapBlock;

	uint16 tagCodeForPGTM = tcProfileGainTableMap;
	
	if (negative.HasProfileGainTableMap () &&

		// If the main profile already has the PGTM attached, let the profile
		// logic take care of writing the PGTM. Otherwise we would end up
		// writing two identical PGTM tags into IFD 0.
		
		(negative.ShareProfileGainTableMap () !=
		 mainProfile.ShareProfileGainTableMap ()))
		{

		dng_memory_stream tempStream (host.Allocator (),
									  host.Sniffer ());

		const auto &gainTableMap = negative.ProfileGainTableMap ();

		gainTableMap.PutStream (tempStream);

		profileGainTableMapBlock.Reset
			(tempStream.AsMemoryBlock (host.Allocator ()));

		if (gainTableMap.RequiresVersion2 ())
			tagCodeForPGTM = tcProfileGainTableMap2;

		}
		
	tag_data_ptr tagProfileGainTableMapBlock
		(tagCodeForPGTM,
		 ttUndefined,
		 profileGainTableMapBlock.Get () ? profileGainTableMapBlock->LogicalSize () : 0,
		 profileGainTableMapBlock.Get () ? profileGainTableMapBlock->Buffer		 () : NULL);
								 
	if (profileGainTableMapBlock.Get ())
		{

		if (tagCodeForPGTM == tcProfileGainTableMap2)
			mainIFD.Add (&tagProfileGainTableMapBlock);

		else
			rawIFD.Add (&tagProfileGainTableMapBlock);

		}

	// ImageSequenceInfo.

	AutoPtr<tiff_tag> tagImageSequenceInfo;

	if (metadata->ImageSequenceInfo ().IsValid ())
		{
		
		tagImageSequenceInfo.Reset
			(metadata->ImageSequenceInfo ().MakeTag (host.Allocator ()));

		mainIFD.Add (tagImageSequenceInfo.Get ());
		
		}

	// ImageStats.

	AutoPtr<tiff_tag> tagImageStats;

		{

		const auto &stats = metadata->ImageStats ();

		if (stats.IsValidForPlaneCount (negative.ColorChannels ()) &&
			(stats.TagCount () > 0))
			{
		
			tagImageStats.Reset (stats.MakeTag (host.Allocator ()));

			rawIFD.Add (tagImageStats.Get ());
		
			}

		}

	// Transparency mask, if any.
	
	AutoPtr<dng_ifd> maskInfo;
	
	AutoPtr<dng_tiff_directory> maskIFD;
	
	AutoPtr<dng_basic_tag_set> maskBasic;
	
	if (hasTransparencyMask)
		{
		
		// Create mask IFD.
		
		maskInfo.Reset (new dng_ifd);
		
		maskInfo->fNewSubFileType = sfTransparencyMask;
		
		maskInfo->fImageWidth  = negative.RawTransparencyMask ()->Bounds ().W ();
		maskInfo->fImageLength = negative.RawTransparencyMask ()->Bounds ().H ();
		
		maskInfo->fSamplesPerPixel = 1;
		
		maskInfo->fBitsPerSample [0] = negative.RawTransparencyMaskBitDepth ();
		
		maskInfo->fPhotometricInterpretation = piTransparencyMask;

		SetEncodeSettingsForIFD (*negative.RawTransparencyMask (),
								 uncompressed,
								 maxBackwardVersion,
								 *maskInfo,
								 rawLossyCompressedTransparencyMask);
			
		// Create mask tiff directory.
			
		maskIFD.Reset (new dng_tiff_directory);
		
		// Add mask basic tag set.
		
		maskBasic.Reset (new dng_basic_tag_set (*maskIFD, *maskInfo));
				
		}
  
	AutoPtr<dng_ifd> depthInfo;
	
	AutoPtr<dng_tiff_directory> depthIFD;
	
	AutoPtr<dng_basic_tag_set> depthBasic;
	
	tag_uint16 tagDepthFormat (tcDepthFormat,
							   (uint16) negative.DepthFormat ());
		
	tag_urational tagDepthNear (tcDepthNear,
								negative.DepthNear ());
		
	tag_urational tagDepthFar (tcDepthFar,
							   negative.DepthFar ());
		
	tag_uint16 tagDepthUnits (tcDepthUnits,
							  (uint16) negative.DepthUnits ());
		
	tag_uint16 tagDepthMeasureType (tcDepthMeasureType,
									(uint16) negative.DepthMeasureType ());
		
	if (hasDepthMap)
		{

		// Create depth IFD.
	
		depthInfo.Reset (new dng_ifd);
	
		depthInfo->fNewSubFileType = sfDepthMap;
	
		depthInfo->fImageWidth	= negative.RawDepthMap ()->Bounds ().W ();
		depthInfo->fImageLength = negative.RawDepthMap ()->Bounds ().H ();
	
		depthInfo->fSamplesPerPixel = 1;
	
		depthInfo->fBitsPerSample [0] = negative.RawDepthMap ()->PixelSize () * 8;
	
		depthInfo->fPhotometricInterpretation = piDepth;

		SetEncodeSettingsForIFD (*negative.RawDepthMap (),
								 uncompressed,
								 maxBackwardVersion,
								 *depthInfo,
								 negative.RawLossyCompressedDepthMap ());

		// Create mask tiff directory.
	
		depthIFD.Reset (new dng_tiff_directory);
	
		// Add mask basic tag set.
	
		depthBasic.Reset (new dng_basic_tag_set (*depthIFD, *depthInfo));
		
		// Depth metadata.
		
		mainIFD.Add (&tagDepthFormat);
		mainIFD.Add (&tagDepthNear);
		mainIFD.Add (&tagDepthFar);
		mainIFD.Add (&tagDepthUnits);
		mainIFD.Add (&tagDepthMeasureType);
	
		}
		
	// Enhanced stage 3 image.
	
	AutoPtr<dng_ifd> enhancedInfo;
	
	AutoPtr<dng_tiff_directory> enhancedIFD;
	
	AutoPtr<dng_basic_tag_set> enhancedBasic;
	
	tag_string enhanceParams (tcEnhanceParams,
							  negative.EnhanceParams (),
							  false);
	
	tag_urational enhanceBaselineSharpness (tcBaselineSharpness,
											negative.BaselineSharpnessR ());
		
	tag_urational enhanceNoiseReductionApplied (tcNoiseReductionApplied,
												negative.NoiseReductionApplied ());
		
	tag_dng_noise_profile enhanceNoiseProfile (negative.NoiseProfile ());
		
	uint16 enhanceBlackLevelData [kMaxColorPlanes];
	
	tag_uint16_ptr enhanceBlackLevel (tcBlackLevel,
									  enhanceBlackLevelData);

	dng_urational enhanceDefaultScaleData [2];
	
	enhanceDefaultScaleData [0] = negative.DefaultScaleH ();
	enhanceDefaultScaleData [1] = negative.DefaultScaleV ();
												
	tag_urational_ptr enhanceDefaultScale (tcDefaultScale,
										   enhanceDefaultScaleData,
										   2);

	tag_urational enhanceBestQualityScale (tcBestQualityScale,
										   negative.BestQualityScale ());
									  
	dng_urational enhanceDefaultCropOriginData [2];

	enhanceDefaultCropOriginData [0] = negative.DefaultCropOriginH ();
	enhanceDefaultCropOriginData [1] = negative.DefaultCropOriginV ();

	tag_urational_ptr enhanceDefaultCropOrigin (tcDefaultCropOrigin,
												enhanceDefaultCropOriginData,
												2);

	dng_urational enhanceDefaultCropSizeData [2];

	enhanceDefaultCropSizeData [0] = negative.DefaultCropSizeH ();
	enhanceDefaultCropSizeData [1] = negative.DefaultCropSizeV ();

	tag_urational_ptr enhanceDefaultCropSize (tcDefaultCropSize,
											  enhanceDefaultCropSizeData,
											  2);

	if (hasEnhancedImage)
		{
		
		// Create enhanced IFD.
	
		enhancedInfo.Reset (new dng_ifd);
	
		enhancedInfo->fNewSubFileType = sfEnhancedImage;
	
		enhancedInfo->fImageWidth  = negative.Stage3Image ()->Bounds ().W ();
		enhancedInfo->fImageLength = negative.Stage3Image ()->Bounds ().H ();
	
		enhancedInfo->fSamplesPerPixel = negative.Stage3Image ()->Planes ();

		const bool isEnhancedFloat =
			(negative.Stage3Image ()->PixelType () == ttFloat);
		
		for (uint32 plane = 0; plane < enhancedInfo->fSamplesPerPixel; plane++)
			{

			if (isEnhancedFloat)
				enhancedInfo->fBitsPerSample [plane] = 16;

			else
				enhancedInfo->fBitsPerSample [plane] =
					negative.Stage3Image ()->PixelSize () * 8;

			}
		
		enhancedInfo->fPhotometricInterpretation = piLinearRaw;

		SetEncodeSettingsForIFD (*negative.Stage3Image (),
								 uncompressed,
								 maxBackwardVersion,
								 *enhancedInfo,
								 negative.EnhancedLossyCompressedImage ());
			
		// Create enhanced tiff directory.
	
		enhancedIFD.Reset (new dng_tiff_directory);
	
		// Add enhanced basic tag set.
	
		enhancedBasic.Reset (new dng_basic_tag_set (*enhancedIFD, *enhancedInfo));
		
		// Add EnhanceParams tag.
		
		enhancedIFD->Add (&enhanceParams);
		
		// Record the enhanced baseline sharpness tag, if different.
		
		if (negative.RawBaselineSharpness () != negative.BaselineSharpnessR ())
			{
			
			enhancedIFD->Add (&enhanceBaselineSharpness);
		
			}
			
		// Record the enhanced noise reduction applied, if different.
		
		if (negative.RawNoiseReductionApplied () != negative.NoiseReductionApplied ())
			{
			
			enhancedIFD->Add (&enhanceNoiseReductionApplied);
		
			}
			
		// Record the enhanced noise profile, if different.
		
		if (negative.RawNoiseProfile () != negative.NoiseProfile ())
			{
			
			enhancedIFD->Add (&enhanceNoiseProfile);
		
			}
			
		// Record stage3 black level.
		
		if (negative.Stage3BlackLevel ())
			{
			
			for (uint32 plane = 0; plane < enhancedInfo->fSamplesPerPixel; plane++)
				{
				
				enhanceBlackLevelData [plane] = negative.Stage3BlackLevel ();

				}
				
			enhanceBlackLevel.SetCount (enhancedInfo->fSamplesPerPixel);
			
			enhancedIFD->Add (&enhanceBlackLevel);

			}
			
		// Record the default scale, if different.
		
		if (negative.RawDefaultScaleH () != negative.DefaultScaleH () ||
			negative.RawDefaultScaleV () != negative.DefaultScaleV ())
			{
			
			enhancedIFD->Add (&enhanceDefaultScale);
			
			}
		
		// Record the best quality scale, if different.
		
		if (negative.RawBestQualityScale () != negative.BestQualityScale ())
			{
			
			enhancedIFD->Add (&enhanceBestQualityScale);

			}
			
		// Record the default crop, if different.
		
		if (negative.RawDefaultCropSizeH   () != negative.DefaultCropSizeH	 () ||
			negative.RawDefaultCropSizeV   () != negative.DefaultCropSizeV	 () ||
			negative.RawDefaultCropOriginH () != negative.DefaultCropOriginH () ||
			negative.RawDefaultCropOriginV () != negative.DefaultCropOriginV ())
			{
			
			enhancedIFD->Add (&enhanceDefaultCropSize);

			enhancedIFD->Add (&enhanceDefaultCropOrigin);

			}
			
		}

	// Semantic masks.

	std::vector<std::unique_ptr<dng_ifd> >			  semanticMaskInfo;
	std::vector<std::unique_ptr<dng_tiff_directory> > semanticMaskIFD;
	std::vector<std::unique_ptr<dng_basic_tag_set> >  semanticMaskBasic;
	std::vector<std::unique_ptr<tag_string> >		  semanticNameTags;
	std::vector<std::unique_ptr<tag_string> >		  semanticInstanceIDTags;
	std::vector<std::unique_ptr<tag_data_ptr> >		  semanticXMPTags;
	std::vector<std::unique_ptr<tag_uint32_ptr> >	  semanticMaskSubAreaTags;

	uint32 semanticMaskCount = 0;
	
	if (negative.HasSemanticMask ())
		{
		
		semanticMaskCount = negative.NumSemanticMasks ();

		semanticMaskInfo	   .resize (semanticMaskCount);
		semanticMaskIFD		   .resize (semanticMaskCount);
		semanticMaskBasic	   .resize (semanticMaskCount);
		semanticNameTags	   .resize (semanticMaskCount);
		semanticInstanceIDTags .resize (semanticMaskCount);
		semanticXMPTags		   .resize (semanticMaskCount);
		semanticMaskSubAreaTags.resize (semanticMaskCount);
		
		for (uint32 j = 0; j < semanticMaskCount; j++)
			{

			semanticMaskInfo [j].reset (new dng_ifd);

			auto &smInfo = *semanticMaskInfo [j];

			smInfo.fNewSubFileType = sfSemanticMask;

			const auto &rawMask = negative.RawSemanticMask (j);
			
			smInfo.fImageWidth	= rawMask.fMask->Width	();
			smInfo.fImageLength = rawMask.fMask->Height ();
	
			smInfo.fSamplesPerPixel = 1;

			smInfo.fBitsPerSample [0] = rawMask.fMask->PixelSize () * 8;

			smInfo.fPhotometricInterpretation = piPhotometricMask;

			SetEncodeSettingsForIFD (*rawMask.fMask,
									 uncompressed,
									 maxBackwardVersion,
									 smInfo,
									 rawMask.fLossyCompressed.get ());

			// Create mask tiff directory.
	
			semanticMaskIFD [j].reset (new dng_tiff_directory);
	
			// Add mask basic tag set.
	
			semanticMaskBasic [j].reset
				(new dng_basic_tag_set (*semanticMaskIFD [j],
										smInfo));

			// Create semantic name, if present.

			if (rawMask.fName.NotEmpty ())
				{
				
				semanticNameTags [j].reset (new tag_string (tcSemanticName,
															rawMask.fName,
															false));

				// Add this tag to the IFD.

				semanticMaskIFD [j]->Add (semanticNameTags [j].get ());
				
				}
			
			// Create semantic instance ID, if present.

			if (rawMask.fInstanceID.NotEmpty ())
				{
				
				semanticInstanceIDTags [j].reset (new tag_string (tcSemanticInstanceID,
																  rawMask.fInstanceID,
																  false));

				// Add this tag to the IFD.

				semanticMaskIFD [j]->Add (semanticInstanceIDTags [j].get ());
				
				}

			// Create XMP block, if present.

			if (rawMask.fXMP)
				{
				
				semanticXMPTags [j].reset (new tag_data_ptr (tcXMP,
															 ttUndefined,
															 rawMask.fXMP->LogicalSize (),
															 rawMask.fXMP->Buffer	   ()));
				
				semanticMaskIFD [j]->Add (semanticXMPTags [j].get ());
				
				}
			
			// Include MaskSubArea tag, if valid.

			if (rawMask.IsMaskSubAreaValid ())
				{
				
				semanticMaskSubAreaTags [j].reset
					(new tag_uint32_ptr (tcMaskSubArea,
										 &rawMask.fMaskSubArea [0],
										 4));

				// Add this tag to the IFD.

				semanticMaskIFD [j]->Add (semanticMaskSubAreaTags [j].get ());
				
				}
			
			}
		
		}

	// Add other subfiles.
		
	uint32 subFileCount = thumbnail ? 1 : 0;
	
	if (hasTransparencyMask)
		{
		subFileCount++;
		}
	
	if (hasDepthMap)
		{
		subFileCount++;
		}
		
	if (hasEnhancedImage)
		{
		subFileCount++;
		}

	if (hasGainMap)
		{
		subFileCount++;
		}

	subFileCount += semanticMaskCount;
	
	// Add previews.
	
	uint32 previewCount = previewList ? previewList->Count () : 0;
	
	std::vector<std::unique_ptr<dng_tiff_directory>> previewIFD (previewCount);
	
	std::vector<std::unique_ptr<dng_basic_tag_set>> previewBasic (previewCount);
	
	for (uint32 j = 0; j < previewCount; j++)
		{
		
		if (thumbnail != &previewList->Preview (j))
			{
		
			previewIFD [j] . reset (new dng_tiff_directory);
			
			previewBasic [j] . reset (previewList->Preview (j).AddTagSet (host,
																		  *previewIFD [j]));
				
			subFileCount++;
			
			}
		
		}
		
	// And a link to the subIFDs.
	
	tag_big_uints tagSubFile (tcSubIFDs,
							  subFileCount);
							   
	if (subFileCount)
		{
	
		mainIFD.Add (&tagSubFile);
		
		}
		
	// Maximum target size of 32-bit DNG files that allows lots of slop for
	// future metadata edits.
	
	const uint64 kMaxTarget32BitDNGSize = 0xFFFFFFFF - 256 * 1024 * 1024;
	
	// Figure out if to use 64-bit version using a multiple pass algorithm.
	
	bool isBigTIFF = false;
	
	uint64 headerSize32 = 0;
	
	uint64 maxNonHeaderSize = 0;
	
	#if qForceWriteBigDNG || qStressTestBigDNG
	allowBigTIFF = true;
	#endif
	
	for (uint32 pass = 1; pass <= 3; pass++)
		{
		
		// Pass 1: Measure header size if 32-bit.
		
		if (pass == 1)
			{
			isBigTIFF = false;
			}
			
		// Pass 2: Make a guess and write non-header data.
		
		else if (pass == 2)
			{
			
			#if qForceWriteBigDNG
			
			isBigTIFF = true;
			
			#else

			if (allowBigTIFF)
				{
				isBigTIFF = (headerSize32 + maxNonHeaderSize > kMaxTarget32BitDNGSize);
				}
				
			#endif

			}
			
		// Pass 3: Use actual file length to decide.
		
		else
			{
			
			#if !qForceWriteBigDNG
			
			if (stream.Length () <= kMaxTarget32BitDNGSize)
				{
				isBigTIFF = false;
				}
				
			else if (stream.Length () > 0xFFFFFFFF)
				{
				DNG_REQUIRE (isBigTIFF, "Bad maxNonHeaderSize estimate");
				}
				
			#endif

			}
			
		// Skip past the header and IFDs for now.
		
		uint64 currentOffset = isBigTIFF ? 16 : 8;
		
		#if qStressTestBigDNG
		currentOffset += kStressTestBytes;
		#endif

		uint64 mainIFDOffset = currentOffset;
		
		mainIFD.SetBigTIFF (isBigTIFF);
		
		currentOffset += mainIFD.Size ();
		
		exifSet.SetBigTIFF (isBigTIFF);

		exifSet.Locate (currentOffset);
		
		currentOffset += exifSet.Size ();

		uint32 subFileIndex = 0;
		
		if (thumbnail)
			{
			
			rawIFD.SetBigTIFF (isBigTIFF);
		
			tagSubFile.Set (subFileIndex++, currentOffset);
		
			currentOffset += rawIFD.Size ();
			
			}
			
		if (hasTransparencyMask)
			{
			
			maskIFD->SetBigTIFF (isBigTIFF);
			
			tagSubFile.Set (subFileIndex++, currentOffset);
			
			currentOffset += maskIFD->Size ();
			
			}
  
		if (hasDepthMap)
			{
			
			depthIFD->SetBigTIFF (isBigTIFF);
			
			tagSubFile.Set (subFileIndex++, currentOffset);
			
			currentOffset += depthIFD->Size ();
			
			}
		
		if (hasEnhancedImage)
			{
			
			enhancedIFD->SetBigTIFF (isBigTIFF);
			
			tagSubFile.Set (subFileIndex++, currentOffset);
			
			currentOffset += enhancedIFD->Size ();
			
			}

		if (hasGainMap)
			{

			gainMapIFD.SetBigTIFF (isBigTIFF);
			
			tagSubFile.Set (subFileIndex++, currentOffset);
			
			currentOffset += gainMapIFD.Size ();
			
			}

		for (uint32 j = 0; j < semanticMaskCount; j++)
			{
			
			semanticMaskIFD [j]->SetBigTIFF (isBigTIFF);

			tagSubFile.Set (subFileIndex++, currentOffset);
			
			currentOffset += semanticMaskIFD [j]->Size ();
			
			}

		for (uint32 j = 0; j < previewCount; j++)
			{
			
			if (thumbnail != &previewList->Preview (j))
				{
				
				previewIFD [j]->SetBigTIFF (isBigTIFF);
		
				tagSubFile.Set (subFileIndex++, currentOffset);

				currentOffset += previewIFD [j]->Size ();
				
				}
			
			}
			
		// Write out the header in pass 3.
		
		if (pass == 3)
			{
			
			stream.SetWritePosition (0);
			
			stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
			
			stream.Put_uint16 (isBigTIFF ? magicBigTIFF : magicTIFF);
			
			if (isBigTIFF)
				{
				stream.Put_uint16 (8);
				stream.Put_uint16 (0);
				stream.Put_uint64 (mainIFDOffset);
				}
			else
				{
				stream.Put_uint32 ((uint32) mainIFDOffset);
				}
			
			#if qStressTestBigDNG
			stream.PutZeros (kStressTestBytes);
			#endif
		
			// Write the IFDs.
			
			mainIFD.Put (stream);
			
			exifSet.Put (stream);
	
			if (thumbnail)
				{
			
				rawIFD.Put (stream);
				
				}
			
			if (hasTransparencyMask)
				{
				
				maskIFD->Put (stream);
				
				}
				
			if (hasDepthMap)
				{
				
				depthIFD->Put (stream);
				
				}
				
			if (hasEnhancedImage)
				{
				
				enhancedIFD->Put (stream);
				
				}

			if (hasGainMap)
				{

				gainMapIFD.Put (stream);
				
				}

			for (uint32 j = 0; j < semanticMaskCount; j++)
				{
					
				semanticMaskIFD [j]->Put (stream);
					
				}

			for (uint32 j = 0; j < previewCount; j++)
				{
				
				if (thumbnail != &previewList->Preview (j))
					{
			
					previewIFD [j]->Put (stream);
					
					}
				
				}
				
			DNG_REQUIRE (currentOffset == stream.Position (),
						 "Header size logic error");
				
			break;		// All done.
			
			} // pass 3
			
		if (pass == 1)
			{
			
			// We figured out the 32-bit header size in pass 1.
		
			headerSize32 = currentOffset;
			
			}
			
		else
			{
			
			// Just zero out the header for now and skip to the non-header.
			
			stream.PutZeros (currentOffset);
			
			}
		
		// Write the extra profiles.
		
		if (extraProfileCount)
			{
			
			for (uint32 j = 0; j < extraProfileCount; j++)
				{
				
				uint32 index = extraProfileIndex [j];
				
				const dng_camera_profile &profile (negative.ProfileByIndex (index));

				tiff_dng_extended_color_profile extraWriter (profile, false);
					
				uint64 estimatedSize = extraWriter.DataSize ();
								
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
					
					}
				
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Extra profile data");
					
					extraProfileTag.Set (j, stream.Position ());

					extraWriter.Put (host, stream);
					
					}
					
				}
			
			}
		
		// Write the thumbnail data.
		
		if (thumbnail)
			{
			
			uint64 estimatedSize = thumbnail->MaxImageDataByteCount ();
								
			if (pass == 1)
				{
				
				maxNonHeaderSize += estimatedSize;
				
				}
				
			else
				{
		
				DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Thumbnail data");
					
				thumbnail->WriteData (host,
									  *this,
									  *thmBasic,
									  stream);
									  
				}
								 
			}
	
		// Write the preview data.
		
		for (uint32 j = 0; j < previewCount; j++)
			{
			
			if (thumbnail != &previewList->Preview (j))
				{
		
				uint64 estimatedSize = previewList->Preview (j).MaxImageDataByteCount ();
								
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
					
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Preview data");
					
					previewList->Preview (j).WriteData (host,
														*this,
														*previewBasic [j],
														stream);
														
					}
													
				}
				
			}
		
		// Write the raw data.
		
		if (rawLossyCompressedImage)
			{

			const auto &img = *rawLossyCompressedImage;
			
			if (pass == 1)
				{
				
				maxNonHeaderSize += img.NonHeaderSize ();
				
				}
				
			else
				{
				
				img.WriteData (stream, rawBasic);

				}
			
			}

		else
			{
			
			uint64 estimatedSize = info.MaxImageDataByteCount ();
			
			if (pass == 1)
				{
				
				maxNonHeaderSize += estimatedSize;
					
				}
				
			else
				{
					
				DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Raw image data");
					
				#if qDNGValidate
				dng_timer timer ("Write raw image time");
				#endif

				WriteImage (host,
							info,
							rawBasic,
							stream,
							rawImage,
							fakeChannels,
							nullptr);

				}
						
			}
		
		// Write transparency mask image.
		
		if (hasTransparencyMask)
			{
			
			// Do we have a lossy compressed version?
			
			if (rawLossyCompressedTransparencyMask)
				{

				const auto &img = *rawLossyCompressedTransparencyMask;
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += img.NonHeaderSize ();
					
					}
					
				else
					{
					
					img.WriteData (stream, *maskBasic);

					}
					
				}
				
			else
				{
				
				uint64 estimatedSize = maskInfo->MaxImageDataByteCount ();
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
						
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Transparency mask data");
						
					#if qDNGValidate
					dng_timer timer ("Write transparency mask time");
					#endif
				
					WriteImage (host,
								*maskInfo,
								*maskBasic,
								stream,
								*negative.RawTransparencyMask ());
								
					}
					
				}
						
			}
	  
		// Write depth map image.
		
		if (hasDepthMap)
			{
			
			// Do we have a lossy compressed version?
			
			if (negative.RawLossyCompressedDepthMap ())
				{

				const auto &img = *(negative.RawLossyCompressedDepthMap ());
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += img.NonHeaderSize ();
					
					}
					
				else
					{
					
					img.WriteData (stream, *depthBasic);
					
					}
					
				}
				
			else
				{
				
				uint64 estimatedSize = depthInfo->MaxImageDataByteCount ();
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
						
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Depth map data");
						
					#if qDNGValidate
					dng_timer timer ("Write depth map time");
					#endif
				
					WriteImage (host,
								*depthInfo,
								*depthBasic,
								stream,
								*negative.RawDepthMap ());
						
					}
					
				}
				
			}

		// Write enhanced image.
		
		if (hasEnhancedImage)
			{
			
			// Do we have a lossy compressed version?
			
			if (negative.EnhancedLossyCompressedImage ())
				{

				const auto &img = *(negative.EnhancedLossyCompressedImage ());
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += img.NonHeaderSize ();
					
					}
					
				else
					{
					
					img.WriteData (stream, *enhancedBasic);
					
					}
					
				}
				
			else
				{

				uint64 estimatedSize = enhancedInfo->MaxImageDataByteCount ();
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
						
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Enhanced image data");
						
					#if qDNGValidate
					dng_timer timer ("Write enhanced image time");
					#endif
				
					WriteImage (host,
								*enhancedInfo,
								*enhancedBasic,
								stream,
								*negative.Stage3Image ());
						
					}
					
				}
				
			}

		// Write gain map.

		if (hasGainMap)
			{

			// Do we have a lossy compressed version?
			
			if (gainMapLossyCompressed)
				{

				const auto &img = *gainMapLossyCompressed;
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += img.NonHeaderSize ();
					
					}
					
				else
					{
					
					img.WriteData (stream, *gainMapTagSet);
					
					}
					
				}
				
			else
				{
				
				uint64 estimatedSize = gainMapImageIFD->MaxImageDataByteCount ();
			
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
						
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream,
											  estimatedSize,
											  "Gain map image data");
						
					#if qDNGValidate
					dng_timer timer ("Write gain map image time");
					#endif
				
					WriteImage (host,
								*gainMapImageIFD,
								*gainMapTagSet,
								stream,
								*gainMapImage);
						
					}

				}
				
			}

		// Write semantic mask images.

		for (uint32 j = 0; j < semanticMaskCount; j++)
			{

			const auto &semanticMask = negative.RawSemanticMask (j);

			// Do we have a lossy compressed version?
			
			if (semanticMask.fLossyCompressed.get ())
				{

				const auto &img = *(semanticMask.fLossyCompressed);
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += img.NonHeaderSize ();
					
					}
					
				else
					{
					
					img.WriteData (stream, *semanticMaskBasic [j]);
					
					}
					
				}
				
			else
				{
				
				uint64 estimatedSize = semanticMaskInfo [j]->MaxImageDataByteCount ();
				
				if (pass == 1)
					{
					
					maxNonHeaderSize += estimatedSize;
						
					}
					
				else
					{
					
					DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Semantic mask data");
						
					WriteImage (host,
								*semanticMaskInfo  [j],
								*semanticMaskBasic [j],
								stream,
								*semanticMask.fMask);
								
					}
					
				}
				
			}

		// Write the big table data.
		
			{
			
			uint64 estimatedSize = bigTableTagSet.DataSize ();
			
			if (pass == 1)
				{
				
				maxNonHeaderSize += estimatedSize;
			
				}
				
			else
				{
			
				DNG_VERIFY_SIZE_ESTIMATE (stream, estimatedSize, "Big table data");
					
				bigTableTagSet.WriteData (stream);
				
				}
				
			}
								
		// Trim the file to this length.
		
		if (pass == 2)
			{
		
			stream.SetLength (stream.Position ());
			
			}
			
		// 32-bit DNG has a 4G size limit.
		
		if (pass == 2 && !isBigTIFF)
			{
			
			if (stream.Length () > 0xFFFFFFFF)
				{
				
				if (allowBigTIFF)
					{
					
					// We screwed up our maxNonHeaderSize estimate.	 Set to
					// the exact value and redo pass 2.
					
					DNG_REPORT ("Really bad maxNonHeaderSize estimate");
					
					maxNonHeaderSize = stream.Length () - headerSize32;
					
					stream.SetWritePosition (0);
					
					stream.SetLength (0);
					
					pass--;		// Redo pass 2
					
					}
					
				else
					{
				
					ThrowImageTooBigDNG ();
					
					}
				
				}

			}
			
		}
	
	stream.Flush ();
	
	}
							   
/*****************************************************************************/
