/*****************************************************************************/ // 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 &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(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 &uncompressedBuffer, AutoPtr &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 &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 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 &compressedBuffer, AutoPtr &uncompressedBuffer, AutoPtr &subTileBlockBuffer, AutoPtr &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 compressedBuffer; AutoPtr uncompressedBuffer; AutoPtr subTileBlockBuffer; AutoPtr 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 compressedBuffer; AutoPtr uncompressedBuffer; AutoPtr subTileBlockBuffer; AutoPtr 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 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 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 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(&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(&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 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 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 profileSet; AutoPtr colorSet; dng_std_vector 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 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 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 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 maskInfo; AutoPtr maskIFD; AutoPtr 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 previewIFD [kMaxDNGPreviews]; AutoPtr 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 (); } /*****************************************************************************/