blob: 5c810426e6e621f8b0ff91a8a0d4574aed4fa786 [file] [log] [blame]
/*****************************************************************************/
// Copyright 2006-2012 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_image_writer.cpp#4 $ */
/* $DateTime: 2012/06/14 20:24:41 $ */
/* $Change: 835078 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_image_writer.h"
#include "dng_abort_sniffer.h"
#include "dng_area_task.h"
#include "dng_bottlenecks.h"
#include "dng_camera_profile.h"
#include "dng_color_space.h"
#include "dng_exif.h"
#include "dng_flags.h"
#include "dng_exceptions.h"
#include "dng_host.h"
#include "dng_ifd.h"
#include "dng_image.h"
#include "dng_jpeg_image.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_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
/*****************************************************************************/
// Defines for testing DNG 1.2 features.
//#define qTestRowInterleave 2
//#define qTestSubTileBlockRows 2
//#define qTestSubTileBlockCols 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
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:
{
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 ());
}
/******************************************************************************/
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 ())
{
sprintf (fData,
"%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 compatiblity 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
}
/******************************************************************************/
void dng_tiff_directory::Add (const tiff_tag *tag)
{
if (fEntries >= kMaxEntries)
{
ThrowProgramError ();
}
// Tags must be sorted in increasing order of tag code.
uint32 index = fEntries;
for (uint32 j = 0; j < fEntries; j++)
{
if (tag->Code () < fTag [j]->Code ())
{
index = j;
break;
}
}
for (uint32 k = fEntries; k > index; k--)
{
fTag [k] = fTag [k - 1];
}
fTag [index] = tag;
fEntries++;
}
/******************************************************************************/
uint32 dng_tiff_directory::Size () const
{
if (!fEntries) return 0;
uint32 size = fEntries * 12 + 6;
for (uint32 index = 0; index < fEntries; index++)
{
uint32 tagSize = fTag [index]->Size ();
if (tagSize > 4)
{
size += (tagSize + 1) & ~1;
}
}
return size;
}
/******************************************************************************/
void dng_tiff_directory::Put (dng_stream &stream,
OffsetsBase offsetsBase,
uint32 explicitBase) const
{
if (!fEntries) return;
uint32 index;
uint32 bigData = fEntries * 12 + 6;
if (offsetsBase == offsetsRelativeToStream)
bigData += (uint32) stream.Position ();
else if (offsetsBase == offsetsRelativeToExplicitBase)
bigData += explicitBase;
stream.Put_uint16 ((uint16) fEntries);
for (index = 0; index < fEntries; index++)
{
const tiff_tag &tag = *fTag [index];
stream.Put_uint16 (tag.Code ());
stream.Put_uint16 (tag.Type ());
stream.Put_uint32 (tag.Count ());
uint32 size = tag.Size ();
if (size <= 4)
{
tag.Put (stream);
while (size < 4)
{
stream.Put_uint8 (0);
size++;
}
}
else
{
stream.Put_uint32 (bigData);
bigData += (size + 1) & ~1;
}
}
stream.Put_uint32 (fChained); // Next IFD offset
for (index = 0; index < fEntries; index++)
{
const tiff_tag &tag = *fTag [index];
uint32 size = tag.Size ();
if (size > 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)
, fBitsPerSample (tcBitsPerSample,
fBitsPerSampleData,
info.fSamplesPerPixel)
, fStrips (info.fUsesStrips)
, fTileWidth (tcTileWidth, info.fTileWidth)
, fTileLength (fStrips ? tcRowsPerStrip : tcTileLength,
info.fTileLength)
, fTileInfoBuffer (info.TilesPerImage (), 8)
, fTileOffsetData (fTileInfoBuffer.Buffer_uint32 ())
, fTileOffsets (fStrips ? tcStripOffsets : tcTileOffsets,
fTileOffsetData,
info.TilesPerImage ())
, fTileByteCountData (fTileOffsetData + info.TilesPerImage ())
, fTileByteCounts (fStrips ? tcStripByteCounts : tcTileByteCounts,
fTileByteCountData,
info.TilesPerImage ())
, fPlanarConfiguration (tcPlanarConfiguration, pcInterleaved)
, fCompression (tcCompression, (uint16) info.fCompression)
, fPredictor (tcPredictor , (uint16) info.fPredictor )
, fExtraSamples (tcExtraSamples,
fExtraSamplesData,
info.fExtraSamplesCount)
, fSampleFormat (tcSampleFormat,
fSampleFormatData,
info.fSamplesPerPixel)
, fRowInterleaveFactor (tcRowInterleaveFactor,
(uint16) info.fRowInterleaveFactor)
, fSubTileBlockSize (tcSubTileBlockSize,
fSubTileBlockSizeData,
2)
{
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.fSubTileBlockRows != 1 ||
info.fSubTileBlockCols != 1)
{
fSubTileBlockSizeData [0] = (uint16) info.fSubTileBlockRows;
fSubTileBlockSizeData [1] = (uint16) info.fSubTileBlockCols;
directory.Add (&fSubTileBlockSize);
}
}
/******************************************************************************/
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)
, 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 ())
, fMake (tcMake, exif.fMake)
, fModel (tcModel, exif.fModel)
, fArtist (tcArtist, exif.fArtist)
, fSoftware (tcSoftware, exif.fSoftware)
, fCopyright (tcCopyright, exif.fCopyright)
, fMakerNoteSafety (tcMakerNoteSafety, makerNoteSafe ? 1 : 0)
, fMakerNote (tcMakerNote, ttUndefined, makerNoteLength, makerNoteData)
, fImageDescription (tcImageDescription, exif.fImageDescription)
, fSerialNumber (tcCameraSerialNumber, exif.fCameraSerialNumber)
, 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 )
, 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.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++)
{
sprintf (fImageUniqueIDData + j * 2,
"%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.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 *
kMaxSamplesPerPixel];
tag_urational_ptr fBlackLevel;
dng_memory_data fBlackLevelDeltaHData;
dng_memory_data fBlackLevelDeltaVData;
tag_srational_ptr fBlackLevelDeltaH;
tag_srational_ptr fBlackLevelDeltaV;
uint16 fWhiteLevelData16 [kMaxSamplesPerPixel];
uint32 fWhiteLevelData32 [kMaxSamplesPerPixel];
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);
}
}
// 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_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 ())
, 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 (fCameraCalibration1.Count () == channels2 ||
fCameraCalibration2.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_matrix fColorMatrix1;
tag_matrix fColorMatrix2;
tag_matrix fForwardMatrix1;
tag_matrix fForwardMatrix2;
tag_matrix fReductionMatrix1;
tag_matrix fReductionMatrix2;
tag_string fProfileName;
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_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;
public:
profile_tag_set (dng_tiff_directory &directory,
const dng_camera_profile &profile);
};
/******************************************************************************/
profile_tag_set::profile_tag_set (dng_tiff_directory &directory,
const dng_camera_profile &profile)
: fCalibrationIlluminant1 (tcCalibrationIlluminant1,
(uint16) profile.CalibrationIlluminant1 ())
, fCalibrationIlluminant2 (tcCalibrationIlluminant2,
(uint16) profile.CalibrationIlluminant2 ())
, fColorMatrix1 (tcColorMatrix1,
profile.ColorMatrix1 ())
, fColorMatrix2 (tcColorMatrix2,
profile.ColorMatrix2 ())
, fForwardMatrix1 (tcForwardMatrix1,
profile.ForwardMatrix1 ())
, fForwardMatrix2 (tcForwardMatrix2,
profile.ForwardMatrix2 ())
, fReductionMatrix1 (tcReductionMatrix1,
profile.ReductionMatrix1 ())
, fReductionMatrix2 (tcReductionMatrix2,
profile.ReductionMatrix2 ())
, fProfileName (tcProfileName,
profile.Name (),
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 ())
, 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)
{
if (profile.HasColorMatrix1 ())
{
uint32 colorChannels = profile.ColorMatrix1 ().Rows ();
directory.Add (&fCalibrationIlluminant1);
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);
directory.Add (&fColorMatrix2);
if (fForwardMatrix2.Count () == colorChannels * 3)
{
directory.Add (&fForwardMatrix2);
}
if (colorChannels > 3 && fReductionMatrix2.Count () == colorChannels * 3)
{
directory.Add (&fReductionMatrix2);
}
}
if (profile.Name ().NotEmpty ())
{
directory.Add (&fProfileName);
}
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 ();
if (haveHueSat1 || haveHueSat2)
{
uint32 hueDivs = 0;
uint32 satDivs = 0;
uint32 valDivs = 0;
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 (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 (SafeUint32Mult(toneCurvePoints, 2),
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);
}
}
}
/******************************************************************************/
tiff_dng_extended_color_profile::tiff_dng_extended_color_profile
(const dng_camera_profile &profile)
: fProfile (profile)
{
}
/******************************************************************************/
void tiff_dng_extended_color_profile::Put (dng_stream &stream,
bool includeModelRestriction)
{
// Profile header.
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (magicExtendedProfile);
stream.Put_uint32 (8);
// Profile tags.
profile_tag_set tagSet (*this, fProfile);
// Camera this profile is for.
tag_string cameraModelTag (tcUniqueCameraModel,
fProfile.UniqueCameraModelRestriction ());
if (includeModelRestriction)
{
if (fProfile.UniqueCameraModelRestriction ().NotEmpty ())
{
Add (&cameraModelTag);
}
}
// Write it all out.
dng_tiff_directory::Put (stream, offsetsRelativeToExplicitBase, 8);
}
/*****************************************************************************/
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 ();
}
}
/*****************************************************************************/
dng_image_writer::dng_image_writer ()
{
}
/*****************************************************************************/
dng_image_writer::~dng_image_writer ()
{
}
/*****************************************************************************/
uint32 dng_image_writer::CompressedBufferSize (const dng_ifd &ifd,
uint32 uncompressedSize)
{
switch (ifd.fCompression)
{
case ccLZW:
{
// Add lots of slop for LZW to expand data.
return SafeUint32Add (SafeUint32Mult (uncompressedSize, 2), 1024);
}
case ccDeflate:
{
// ZLib says maximum is source size + 0.1% + 12 bytes.
return SafeUint32Add (SafeUint32Add (uncompressedSize,
uncompressedSize >> 8), 64);
}
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 SafeUint32Mult (uncompressedSize, 2);
}
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");
}
uint32 tempBufferSize = SafeUint32Mult (
static_cast<uint32>(buffer.fRowStep),
buffer.fPixelSize);
if (!tempBuffer.Get () || tempBuffer->LogicalSize () < tempBufferSize)
{
tempBuffer.Reset (host.Allocate (tempBufferSize));
}
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:
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 fDstCount;
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);
// Hidden copy constructor and assignment operator.
dng_lzw_compressor (const dng_lzw_compressor &compressor);
dng_lzw_compressor & operator= (const dng_lzw_compressor &compressor);
};
/******************************************************************************/
dng_lzw_compressor::dng_lzw_compressor ()
: fBuffer ()
, fTable (NULL)
, fDstPtr (NULL)
, fDstCount (0)
, 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
/*****************************************************************************/
void dng_image_writer::WriteData (dng_host &host,
const dng_ifd &ifd,
dng_stream &stream,
dng_pixel_buffer &buffer,
AutoPtr<dng_memory_block> &compressedBuffer)
{
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 ())
{
DNG_REPORT ("Compression output buffer overflow");
ThrowProgramError ();
}
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);
}
EncodeLosslessJPEG ((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
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.fPreviewSize = image.Size ();
if (image.Planes () == 1)
{
preview.fPhotometricInterpretation = piBlackIsZero;
}
else
{
preview.fPhotometricInterpretation = piYCbCr;
preview.fYCbCrSubSampling.h = cinfo.comp_info [0].h_samp_factor;
preview.fYCbCrSubSampling.v = 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;
}
preview.fCompressedData.Reset (stream.AsMemoryBlock (host.Allocator ()));
#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)
{
// Create pixel buffer to hold uncompressed tile.
dng_pixel_buffer buffer (tileArea, 0, ifd.fSamplesPerPixel,
image.PixelType(), pcInterleaved, uncompressedBuffer->Buffer());
// Get the uncompressed data.
image.Get (buffer, 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);
}
/*****************************************************************************/
class dng_write_tiles_task : public dng_area_task
{
private:
dng_image_writer &fImageWriter;
dng_host &fHost;
const dng_ifd &fIFD;
dng_basic_tag_set &fBasic;
dng_stream &fStream;
const dng_image &fImage;
uint32 fFakeChannels;
uint32 fTilesDown;
uint32 fTilesAcross;
uint32 fCompressedSize;
uint32 fUncompressedSize;
dng_mutex fMutex1;
uint32 fNextTileIndex;
dng_mutex fMutex2;
dng_condition fCondition;
bool fTaskFailed;
uint32 fWriteTileIndex;
public:
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)
: fImageWriter (imageWriter)
, fHost (host)
, fIFD (ifd)
, fBasic (basic)
, fStream (stream)
, fImage (image)
, fFakeChannels (fakeChannels)
, fTilesDown (tilesDown)
, fTilesAcross (tilesAcross)
, fCompressedSize (compressedSize)
, fUncompressedSize (uncompressedSize)
, fMutex1 ("dng_write_tiles_task_1")
, fNextTileIndex (0)
, fMutex2 ("dng_write_tiles_task_2")
, fCondition ()
, fTaskFailed (false)
, fWriteTileIndex (0)
{
fMinTaskArea = 16 * 16;
fUnitCell = dng_point (16, 16);
fMaxTileSize = dng_point (16, 16);
}
void 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.
uint32 tileIndex;
{
dng_lock_mutex lock (&fMutex1);
if (fNextTileIndex == fTilesDown * fTilesAcross)
{
return;
}
tileIndex = fNextTileIndex++;
}
dng_abort_sniffer::SniffForAbort (sniffer);
// Compress tile.
uint32 rowIndex = tileIndex / fTilesAcross;
uint32 colIndex = tileIndex - rowIndex * fTilesAcross;
dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex);
dng_memory_stream tileStream (fHost.Allocator ());
tileStream.SetLittleEndian (fStream.LittleEndian ());
dng_host host (&fHost.Allocator (),
sniffer);
fImageWriter.WriteTile (host,
fIFD,
tileStream,
fImage,
tileArea,
fFakeChannels,
compressedBuffer,
uncompressedBuffer,
subTileBlockBuffer,
tempBuffer);
tileStream.Flush ();
uint32 tileByteCount = (uint32) tileStream.Length ();
tileStream.SetReadPosition (0);
// Wait until it is our turn to write tile.
{
dng_lock_mutex lock (&fMutex2);
while (!fTaskFailed &&
fWriteTileIndex != tileIndex)
{
fCondition.Wait (fMutex2);
}
// If the task failed in another thread, that thread already threw an exception.
if (fTaskFailed)
return;
}
dng_abort_sniffer::SniffForAbort (sniffer);
// Remember this offset.
uint32 tileOffset = (uint32) 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);
}
// Let other threads know it is safe to write to stream.
{
dng_lock_mutex lock (&fMutex2);
// 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 (&fMutex2);
needBroadcast = !fTaskFailed;
fTaskFailed = true;
}
if (needBroadcast)
fCondition.Broadcast ();
throw;
}
}
private:
// Hidden copy constructor and assignment operator.
dng_write_tiles_task (const dng_write_tiles_task &);
dng_write_tiles_task & operator= (const dng_write_tiles_task &);
};
/*****************************************************************************/
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)
{
// Deal with row interleaved images.
if (ifd.fRowInterleaveFactor > 1 &&
ifd.fRowInterleaveFactor < ifd.fImageLength)
{
dng_ifd tempIFD (ifd);
tempIFD.fRowInterleaveFactor = 1;
dng_row_interleaved_image tempImage (*((dng_image *) &image),
ifd.fRowInterleaveFactor);
WriteImage (host,
tempIFD,
basic,
stream,
tempImage,
fakeChannels);
return;
}
// Compute basic information.
uint32 bytesPerSample = TagTypeSize (image.PixelType ());
uint32 bytesPerPixel = SafeUint32Mult (ifd.fSamplesPerPixel,
bytesPerSample);
uint32 tileRowBytes = SafeUint32Mult (ifd.fTileWidth, bytesPerPixel);
// 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,
ifd.fTileLength);
// Don't split sub-tiles across subTileBlocks.
subTileLength = subTileLength / ifd.fSubTileBlockRows
* ifd.fSubTileBlockRows;
}
// Find size of uncompressed buffer.
uint32 uncompressedSize = SafeUint32Mult(subTileLength, tileRowBytes);
// Find size of compressed buffer, if required.
uint32 compressedSize = CompressedBufferSize (ifd, uncompressedSize);
// 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 qImagecore
useMultipleThreads = false;
#endif
if (useMultipleThreads)
{
uint32 threadCount = Min_uint32 (tilesDown * tilesAcross,
host.PerformAreaTaskThreads ());
dng_write_tiles_task task (*this,
host,
ifd,
basic,
stream,
image,
fakeChannels,
tilesDown,
tilesAcross,
compressedSize,
uncompressedSize);
host.PerformAreaTask (task,
dng_rect (0, 0, 16, 16 * threadCount));
}
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)
{
uncompressedBuffer.Reset (host.Allocate (uncompressedSize));
}
if (ifd.fSubTileBlockRows > 1 && uncompressedSize)
{
subTileBlockBuffer.Reset (host.Allocate (uncompressedSize));
}
// Write out each tile.
uint32 tileIndex = 0;
for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
{
for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
{
// Remember this offset.
uint32 tileOffset = (uint32) 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;
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,
stream,
image,
subArea,
fakeChannels,
compressedBuffer,
uncompressedBuffer,
subTileBlockBuffer,
tempBuffer);
}
// Update tile count.
uint32 tileByteCount = (uint32) stream.Position () - tileOffset;
basic.SetTileByteCount (tileIndex, tileByteCount);
tileIndex++;
// Keep the tiles on even byte offsets.
if (tileByteCount & 1)
{
stream.Put_uint8 (0);
}
}
}
}
}
/*****************************************************************************/
#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
/*****************************************************************************/
void dng_image_writer::CleanUpMetadata (dng_host &host,
dng_metadata &metadata,
dng_metadata_subset metadataSubset,
const char *dstMIMI,
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.SourceMIMI ().Get (),
dstMIMI);
#else
metadata.UpdateDateTimeToNow ();
#endif
// Update EXIF version to at least 2.3 so all the exif tags
// can be written.
if (newEXIF.fExifVersion < DNG_CHAR4 ('0','2','3','0'))
{
newEXIF.fExifVersion = DNG_CHAR4 ('0','2','3','0');
newXMP.Set (XMP_NS_EXIF, "ExifVersion", "0230");
}
// 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 == kMetadataSubset_CopyrightOnly ||
metadataSubset == kMetadataSubset_CopyrightAndContact)
{
dng_xmp oldXMP (newXMP );
dng_exif oldEXIF (newEXIF);
// For these options, we start from nothing, and only fill in the
// fields that we absolutely need.
newXMP.RemoveProperties (NULL);
newEXIF.SetEmpty ();
metadata.ClearMakerNote ();
// Move copyright related fields over.
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");
#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
// Copyright and Contact adds the contact info fields.
if (metadataSubset == kMetadataSubset_CopyrightAndContact)
{
// 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");
CopyAltLangDefault (oldXMP,
newXMP,
XMP_NS_DC,
"title");
}
}
else if (metadataSubset == kMetadataSubset_AllExceptCameraInfo ||
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation ||
metadataSubset == kMetadataSubset_AllExceptLocationInfo)
{
dng_xmp oldXMP (newXMP );
dng_exif oldEXIF (newEXIF);
if (metadataSubset == kMetadataSubset_AllExceptCameraInfo ||
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation)
{
// 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_AUX);
// Remove Camera Raw info
newXMP.RemoveProperties (XMP_NS_CRS);
newXMP.RemoveProperties (XMP_NS_CRSS);
newXMP.RemoveProperties (XMP_NS_CRX);
// Remove DocOps history, since it contains the original
// camera format.
newXMP.Remove (XMP_NS_MM, "History");
// MakerNote contains camera info.
metadata.ClearMakerNote ();
}
if (metadataSubset == kMetadataSubset_AllExceptLocationInfo ||
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation)
{
// 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");
}
}
// Rebuild the legacy IPTC block, if needed.
bool isTIFF = (strcmp (dstMIMI, "image/tiff") == 0);
bool isDNG = (strcmp (dstMIMI, "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);
// 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
}
/*****************************************************************************/
void dng_image_writer::WriteTIFF (dng_host &host,
dng_stream &stream,
const dng_image &image,
uint32 photometricInterpretation,
uint32 compression,
dng_negative *negative,
const dng_color_space *space,
const dng_resolution *resolution,
const dng_jpeg_preview *thumbnail,
const dng_memory_block *imageResources,
dng_metadata_subset metadataSubset)
{
WriteTIFF (host,
stream,
image,
photometricInterpretation,
compression,
negative ? &(negative->Metadata ()) : NULL,
space,
resolution,
thumbnail,
imageResources,
metadataSubset);
}
/*****************************************************************************/
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)
{
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);
}
/*****************************************************************************/
void dng_image_writer::WriteTIFFWithProfile (dng_host &host,
dng_stream &stream,
const dng_image &image,
uint32 photometricInterpretation,
uint32 compression,
dng_negative *negative,
const void *profileData,
uint32 profileSize,
const dng_resolution *resolution,
const dng_jpeg_preview *thumbnail,
const dng_memory_block *imageResources,
dng_metadata_subset metadataSubset)
{
WriteTIFFWithProfile (host,
stream,
image,
photometricInterpretation,
compression,
negative ? &(negative->Metadata ()) : NULL,
profileData,
profileSize,
resolution,
thumbnail,
imageResources,
metadataSubset);
}
/*****************************************************************************/
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)
{
uint32 j;
AutoPtr<dng_metadata> metadata;
if (constMetadata)
{
metadata.Reset (constMetadata->Clone (host.Allocator ()));
CleanUpMetadata (host,
*metadata,
metadataSubset,
"image/tiff");
}
dng_ifd ifd;
ifd.fNewSubFileType = sfMainImage;
ifd.fImageWidth = image.Bounds ().W ();
ifd.fImageLength = image.Bounds ().H ();
ifd.fSamplesPerPixel = image.Planes ();
ifd.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
for (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
{
ifd.FindStripSize (128 * 1024);
ifd.fPredictor = cpHorizontalDifference;
}
uint32 extraSamples = 0;
switch (photometricInterpretation)
{
case piBlackIsZero:
{
extraSamples = image.Planes () - 1;
break;
}
case piRGB:
{
extraSamples = image.Planes () - 3;
break;
}
default:
break;
}
ifd.fExtraSamplesCount = extraSamples;
if (image.PixelType () == ttFloat)
{
for (j = 0; j < ifd.fSamplesPerPixel; j++)
{
ifd.fSampleFormat [j] = sfFloatingPoint;
}
}
dng_tiff_directory mainIFD;
dng_basic_tag_set basic (mainIFD, ifd);
// 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.
exif_tag_set exifSet (mainIFD,
metadata.Get () && metadata->GetExif () ? *metadata->GetExif ()
: dng_exif (),
metadata.Get () ? metadata->IsMakerNoteSafe () : false,
metadata.Get () ? metadata->MakerNoteData () : NULL,
metadata.Get () ? metadata->MakerNoteLength () : 0,
false);
// Find offset to main image data.
uint32 offsetMainIFD = 8;
uint32 offsetExifData = offsetMainIFD + mainIFD.Size ();
exifSet.Locate (offsetExifData);
uint32 offsetMainData = offsetExifData + exifSet.Size ();
stream.SetWritePosition (offsetMainData);
// Write the main image data.
WriteImage (host,
ifd,
basic,
stream,
image);
// Trim the file to this length.
stream.SetLength (stream.Position ());
// TIFF has a 4G size limit.
if (stream.Length () > 0x0FFFFFFFFL)
{
ThrowImageTooBigTIFF ();
}
// Write TIFF Header.
stream.SetWritePosition (0);
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (42);
stream.Put_uint32 (offsetMainIFD);
// Write the IFDs.
mainIFD.Put (stream);
exifSet.Put (stream);
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)
{
WriteDNG (host,
stream,
negative,
negative.Metadata (),
previewList,
maxBackwardVersion,
uncompressed);
}
/*****************************************************************************/
void dng_image_writer::WriteDNG (dng_host &host,
dng_stream &stream,
const dng_negative &negative,
const dng_metadata &constMetadata,
const dng_preview_list *previewList,
uint32 maxBackwardVersion,
bool uncompressed)
{
uint32 j;
// Clean up metadata per MWG recommendations.
AutoPtr<dng_metadata> metadata (constMetadata.Clone (host.Allocator ()));
CleanUpMetadata (host,
*metadata,
kMetadataSubset_All,
"image/dng");
// Figure out the compression to use. Most of the time this is lossless
// JPEG.
uint32 compression = uncompressed ? ccUncompressed : ccJPEG;
// Was the the original file lossy JPEG compressed?
const dng_jpeg_image *rawJPEGImage = negative.RawJPEGImage ();
// If so, can we save it using the requested compression and DNG version?
if (uncompressed || maxBackwardVersion < dngVersion_1_4_0_0)
{
if (rawJPEGImage || negative.RawJPEGImageDigest ().IsValid ())
{
rawJPEGImage = NULL;
negative.ClearRawJPEGImageDigest ();
negative.ClearRawImageDigest ();
}
}
else if (rawJPEGImage)
{
compression = ccLossyJPEG;
}
// 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 () !=
defaultFinalSize);
saveOriginalDefaultCropSize = (negative.OriginalDefaultCropSizeV () !=
dng_urational (defaultFinalSize.v, 1)) ||
(negative.OriginalDefaultCropSizeH () !=
dng_urational (defaultFinalSize.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 ());
}
}
// Is this a floating point image that we are saving?
bool isFloatingPoint = (negative.RawImage ().PixelType () == ttFloat);
// Does this image have a transparency mask?
bool hasTransparencyMask = (negative.RawTransparencyMask () != NULL);
// Should we save a compressed 32-bit integer file?
bool isCompressed32BitInteger = (negative.RawImage ().PixelType () == ttLong) &&
(maxBackwardVersion >= dngVersion_1_4_0_0) &&
(!uncompressed);
// Figure out what main version to use.
uint32 dngVersion = dngVersion_Current;
// Don't write version 1.4 files unless we actually use some feature of the 1.4 spec.
if (dngVersion == dngVersion_1_4_0_0)
{
if (!rawJPEGImage &&
!isFloatingPoint &&
!hasTransparencyMask &&
!isCompressed32BitInteger &&
!saveOriginalDefaultFinalSize &&
!saveOriginalBestQualityFinalSize &&
!saveOriginalDefaultCropSize )
{
dngVersion = dngVersion_1_3_0_0;
}
}
// Figure out what backward version to use.
uint32 dngBackwardVersion = dngVersion_1_1_0_0;
#if defined(qTestRowInterleave) || defined(qTestSubTileBlockRows) || defined(qTestSubTileBlockCols)
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_2_0_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 (rawJPEGImage || isFloatingPoint || hasTransparencyMask || isCompressed32BitInteger)
{
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_4_0_0);
}
if (dngBackwardVersion > dngVersion)
{
ThrowProgramError ();
}
// Find best thumbnail from preview list, if any.
const dng_preview *thumbnail = NULL;
if (previewList)
{
uint32 thumbArea = 0;
for (j = 0; j < previewList->Count (); j++)
{
const dng_image_preview *imagePreview = dynamic_cast<const dng_image_preview *>(&previewList->Preview (j));
if (imagePreview)
{
uint32 thisArea = imagePreview->fImage->Bounds ().W () *
imagePreview->fImage->Bounds ().H ();
if (!thumbnail || thisArea < thumbArea)
{
thumbnail = &previewList->Preview (j);
thumbArea = thisArea;
}
}
const dng_jpeg_preview *jpegPreview = dynamic_cast<const dng_jpeg_preview *>(&previewList->Preview (j));
if (jpegPreview)
{
uint32 thisArea = jpegPreview->fPreviewSize.h *
jpegPreview->fPreviewSize.v;
if (!thumbnail || thisArea < thumbArea)
{
thumbnail = &previewList->Preview (j);
thumbArea = thisArea;
}
}
}
}
// Create the main IFD
dng_tiff_directory mainIFD;
// 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 (mainIFD));
}
// Get the raw image we are writing.
const dng_image &rawImage (negative.RawImage ());
// For floating point, we only support ZIP compression.
if (isFloatingPoint && !uncompressed)
{
compression = ccDeflate;
}
// For 32-bit integer images, we only support ZIP and uncompressed.
if (rawImage.PixelType () == ttLong)
{
if (isCompressed32BitInteger)
{
compression = ccDeflate;
}
else
{
compression = ccUncompressed;
}
}
// Get a copy of the mosaic info.
dng_mosaic_info mosaicInfo;
if (negative.GetMosaicInfo ())
{
mosaicInfo = *(negative.GetMosaicInfo ());
}
// 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 (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 (rawJPEGImage)
{
DNG_ASSERT (rawPixelType == ttByte,
"Unexpected jpeg pixel type");
DNG_ASSERT (info.fImageWidth == (uint32) rawJPEGImage->fImageSize.h &&
info.fImageLength == (uint32) rawJPEGImage->fImageSize.v,
"Unexpected jpeg image size");
info.fTileWidth = rawJPEGImage->fTileSize.h;
info.fTileLength = rawJPEGImage->fTileSize.v;
info.fUsesStrips = rawJPEGImage->fUsesStrips;
info.fUsesTiles = !info.fUsesStrips;
}
else if (info.fCompression == ccJPEG)
{
info.FindTileSize (128 * 1024);
}
else if (info.fCompression == ccDeflate)
{
info.FindTileSize (512 * 1024);
}
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;
#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);
// JPEG tables, if any.
tag_data_ptr tagJPEGTables (tcJPEGTables,
ttUndefined,
0,
NULL);
if (rawJPEGImage && rawJPEGImage->fJPEGTables.Get ())
{
tagJPEGTables.SetData (rawJPEGImage->fJPEGTables->Buffer ());
tagJPEGTables.SetCount (rawJPEGImage->fJPEGTables->LogicalSize ());
rawIFD.Add (&tagJPEGTables);
}
// DefaultScale tag.
dng_urational defaultScaleData [2];
defaultScaleData [0] = negative.DefaultScaleH ();
defaultScaleData [1] = negative.DefaultScaleV ();
tag_urational_ptr tagDefaultScale (tcDefaultScale,
defaultScaleData,
2);
rawIFD.Add (&tagDefaultScale);
// Best quality scale tag.
tag_urational tagBestQualityScale (tcBestQualityScale,
negative.BestQualityScale ());
rawIFD.Add (&tagBestQualityScale);
// DefaultCropOrigin tag.
dng_urational defaultCropOriginData [2];
defaultCropOriginData [0] = negative.DefaultCropOriginH ();
defaultCropOriginData [1] = negative.DefaultCropOriginV ();
tag_urational_ptr tagDefaultCropOrigin (tcDefaultCropOrigin,
defaultCropOriginData,
2);
rawIFD.Add (&tagDefaultCropOrigin);
// DefaultCropSize tag.
dng_urational defaultCropSizeData [2];
defaultCropSizeData [0] = negative.DefaultCropSizeH ();
defaultCropSizeData [1] = 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);
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);
}
// Profile and other color related tags.
AutoPtr<profile_tag_set> profileSet;
AutoPtr<color_tag_set> colorSet;
dng_std_vector<uint32> extraProfileIndex;
if (!negative.IsMonochrome ())
{
const dng_camera_profile &mainProfile (*negative.ComputeCameraProfileToEmbed (constMetadata));
profileSet.Reset (new profile_tag_set (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 != &mainProfile)
{
if (profile.WasReadFromDNG ())
{
extraProfileIndex.push_back (index);
}
}
}
}
// Extra camera profiles tag.
uint32 extraProfileCount = (uint32) extraProfileIndex.size ();
dng_memory_data extraProfileOffsets (extraProfileCount, sizeof (uint32));
tag_uint32_ptr extraProfileTag (tcExtraCameraProfiles,
extraProfileOffsets.Buffer_uint32 (),
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);
tag_urational tagNoiseReductionApplied (tcNoiseReductionApplied,
negative.NoiseReductionApplied ());
if (negative.NoiseReductionApplied ().IsValid ())
{
mainIFD.Add (&tagNoiseReductionApplied);
}
tag_dng_noise_profile tagNoiseProfile (negative.NoiseProfile ());
if (negative.NoiseProfile ().IsValidForNegative (negative))
{
mainIFD.Add (&tagNoiseProfile);
}
tag_urational tagBaselineSharpness (tcBaselineSharpness,
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.ColorimetricReference () != crSceneReferred)
{
mainIFD.Add (&tagColorimetricReference);
}
bool useNewDigest = (maxBackwardVersion >= dngVersion_1_4_0_0);
if (compression == ccLossyJPEG)
{
negative.FindRawJPEGImageDigest (host);
}
else
{
if (useNewDigest)
{
negative.FindNewRawImageDigest (host);
}
else
{
negative.FindRawImageDigest (host);
}
}
tag_uint8_ptr tagRawImageDigest (useNewDigest ? tcNewRawImageDigest : tcRawImageDigest,
compression == ccLossyJPEG ?
negative.RawJPEGImageDigest ().data :
(useNewDigest ? negative.NewRawImageDigest ().data
: negative.RawImageDigest ().data),
16);
mainIFD.Add (&tagRawImageDigest);
negative.FindRawDataUniqueID (host);
tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID,
negative.RawDataUniqueID ().data,
16);
if (negative.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);
}
// 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;
maskInfo->fCompression = uncompressed ? ccUncompressed : ccDeflate;
maskInfo->fPredictor = uncompressed ? cpNullPredictor : cpHorizontalDifference;
if (negative.RawTransparencyMask ()->PixelType () == ttFloat)
{
maskInfo->fSampleFormat [0] = sfFloatingPoint;
if (maskInfo->fCompression == ccDeflate)
{
maskInfo->fPredictor = cpFloatingPoint;
}
}
if (maskInfo->fCompression == ccDeflate)
{
maskInfo->FindTileSize (512 * 1024);
}
else
{
maskInfo->SetSingleStrip ();
}
// Create mask tiff directory.
maskIFD.Reset (new dng_tiff_directory);
// Add mask basic tag set.
maskBasic.Reset (new dng_basic_tag_set (*maskIFD, *maskInfo));
}
// Add other subfiles.
uint32 subFileCount = thumbnail ? 1 : 0;
if (hasTransparencyMask)
{
subFileCount++;
}
// Add previews.
uint32 previewCount = previewList ? previewList->Count () : 0;
AutoPtr<dng_tiff_directory> previewIFD [kMaxDNGPreviews];
AutoPtr<dng_basic_tag_set> previewBasic [kMaxDNGPreviews];
for (j = 0; j < previewCount; j++)
{
if (thumbnail != &previewList->Preview (j))
{
previewIFD [j] . Reset (new dng_tiff_directory);
previewBasic [j] . Reset (previewList->Preview (j).AddTagSet (*previewIFD [j]));
subFileCount++;
}
}
// And a link to the raw and JPEG image IFDs.
uint32 subFileData [kMaxDNGPreviews + 2];
tag_uint32_ptr tagSubFile (tcSubIFDs,
subFileData,
subFileCount);
if (subFileCount)
{
mainIFD.Add (&tagSubFile);
}
// Skip past the header and IFDs for now.
uint32 currentOffset = 8;
currentOffset += mainIFD.Size ();
uint32 subFileIndex = 0;
if (thumbnail)
{
subFileData [subFileIndex++] = currentOffset;
currentOffset += rawIFD.Size ();
}
if (hasTransparencyMask)
{
subFileData [subFileIndex++] = currentOffset;
currentOffset += maskIFD->Size ();
}
for (j = 0; j < previewCount; j++)
{
if (thumbnail != &previewList->Preview (j))
{
subFileData [subFileIndex++] = currentOffset;
currentOffset += previewIFD [j]->Size ();
}
}
exifSet.Locate (currentOffset);
currentOffset += exifSet.Size ();
stream.SetWritePosition (currentOffset);
// Write the extra profiles.
if (extraProfileCount)
{
for (j = 0; j < extraProfileCount; j++)
{
extraProfileOffsets.Buffer_uint32 () [j] = (uint32) stream.Position ();
uint32 index = extraProfileIndex [j];
const dng_camera_profile &profile (negative.ProfileByIndex (index));
tiff_dng_extended_color_profile extraWriter (profile);
extraWriter.Put (stream, false);
}
}
// Write the thumbnail data.
if (thumbnail)
{
thumbnail->WriteData (host,
*this,
*thmBasic,
stream);
}
// Write the preview data.
for (j = 0; j < previewCount; j++)
{
if (thumbnail != &previewList->Preview (j))
{
previewList->Preview (j).WriteData (host,
*this,
*previewBasic [j],
stream);
}
}
// Write the raw data.
if (rawJPEGImage)
{
uint32 tileCount = info.TilesAcross () *
info.TilesDown ();
for (uint32 tileIndex = 0; tileIndex < tileCount; tileIndex++)
{
// Remember this offset.
uint32 tileOffset = (uint32) stream.Position ();
rawBasic.SetTileOffset (tileIndex, tileOffset);
// Write JPEG data.
stream.Put (rawJPEGImage->fJPEGData [tileIndex]->Buffer (),
rawJPEGImage->fJPEGData [tileIndex]->LogicalSize ());
// Update tile count.
uint32 tileByteCount = (uint32) stream.Position () - tileOffset;
rawBasic.SetTileByteCount (tileIndex, tileByteCount);
// Keep the tiles on even byte offsets.
if (tileByteCount & 1)
{
stream.Put_uint8 (0);
}
}
}
else
{
#if qDNGValidate
dng_timer timer ("Write raw image time");
#endif
WriteImage (host,
info,
rawBasic,
stream,
rawImage,
fakeChannels);
}
// Write transparency mask image.
if (hasTransparencyMask)
{
#if qDNGValidate
dng_timer timer ("Write transparency mask time");
#endif
WriteImage (host,
*maskInfo,
*maskBasic,
stream,
*negative.RawTransparencyMask ());
}
// Trim the file to this length.
stream.SetLength (stream.Position ());
// DNG has a 4G size limit.
if (stream.Length () > 0x0FFFFFFFFL)
{
ThrowImageTooBigDNG ();
}
// Write TIFF Header.
stream.SetWritePosition (0);
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (42);
stream.Put_uint32 (8);
// Write the IFDs.
mainIFD.Put (stream);
if (thumbnail)
{
rawIFD.Put (stream);
}
if (hasTransparencyMask)
{
maskIFD->Put (stream);
}
for (j = 0; j < previewCount; j++)
{
if (thumbnail != &previewList->Preview (j))
{
previewIFD [j]->Put (stream);
}
}
exifSet.Put (stream);
stream.Flush ();
}
/*****************************************************************************/