/*****************************************************************************/ // 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_negative.cpp#3 $ */ /* $DateTime: 2012/06/14 20:24:41 $ */ /* $Change: 835078 $ */ /* $Author: tknoll $ */ /*****************************************************************************/ #include "dng_negative.h" #include "dng_1d_table.h" #include "dng_abort_sniffer.h" #include "dng_area_task.h" #include "dng_assertions.h" #include "dng_bottlenecks.h" #include "dng_camera_profile.h" #include "dng_color_space.h" #include "dng_color_spec.h" #include "dng_exceptions.h" #include "dng_globals.h" #include "dng_host.h" #include "dng_image.h" #include "dng_image_writer.h" #include "dng_info.h" #include "dng_jpeg_image.h" #include "dng_linearization_info.h" #include "dng_memory.h" #include "dng_memory_stream.h" #include "dng_misc_opcodes.h" #include "dng_mosaic_info.h" #include "dng_preview.h" #include "dng_resample.h" #include "dng_safe_arithmetic.h" #include "dng_simple_image.h" #include "dng_tag_codes.h" #include "dng_tag_values.h" #include "dng_tile_iterator.h" #include "dng_utils.h" #if qDNGUseXMP #include "dng_xmp.h" #endif /*****************************************************************************/ dng_noise_profile::dng_noise_profile () : fNoiseFunctions () { } /*****************************************************************************/ dng_noise_profile::dng_noise_profile (const dng_std_vector &functions) : fNoiseFunctions (functions) { } /*****************************************************************************/ bool dng_noise_profile::IsValid () const { if (NumFunctions () == 0 || NumFunctions () > kMaxColorPlanes) { return false; } for (uint32 plane = 0; plane < NumFunctions (); plane++) { if (!NoiseFunction (plane).IsValid ()) { return false; } } return true; } /*****************************************************************************/ bool dng_noise_profile::IsValidForNegative (const dng_negative &negative) const { if (!(NumFunctions () == 1 || NumFunctions () == negative.ColorChannels ())) { return false; } return IsValid (); } /*****************************************************************************/ const dng_noise_function & dng_noise_profile::NoiseFunction (uint32 plane) const { if (NumFunctions () == 1) { return fNoiseFunctions.front (); } DNG_REQUIRE (plane < NumFunctions (), "Bad plane index argument for NoiseFunction ()."); return fNoiseFunctions [plane]; } /*****************************************************************************/ uint32 dng_noise_profile::NumFunctions () const { return (uint32) fNoiseFunctions.size (); } /*****************************************************************************/ dng_metadata::dng_metadata (dng_host &host) : fHasBaseOrientation (false) , fBaseOrientation () , fIsMakerNoteSafe (false) , fMakerNote () , fExif (host.Make_dng_exif ()) , fOriginalExif () , fIPTCBlock () , fIPTCOffset (kDNGStreamInvalidOffset) #if qDNGUseXMP , fXMP (host.Make_dng_xmp ()) #endif , fEmbeddedXMPDigest () , fXMPinSidecar (false) , fXMPisNewer (false) , fSourceMIMI () { } /*****************************************************************************/ dng_metadata::~dng_metadata () { } /******************************************************************************/ template< class T > T * CloneAutoPtr (const AutoPtr< T > &ptr) { return ptr.Get () ? ptr->Clone () : NULL; } /******************************************************************************/ template< class T, typename U > T * CloneAutoPtr (const AutoPtr< T > &ptr, U &u) { return ptr.Get () ? ptr->Clone (u) : NULL; } /******************************************************************************/ dng_metadata::dng_metadata (const dng_metadata &rhs, dng_memory_allocator &allocator) : fHasBaseOrientation (rhs.fHasBaseOrientation) , fBaseOrientation (rhs.fBaseOrientation) , fIsMakerNoteSafe (rhs.fIsMakerNoteSafe) , fMakerNote (CloneAutoPtr (rhs.fMakerNote, allocator)) , fExif (CloneAutoPtr (rhs.fExif)) , fOriginalExif (CloneAutoPtr (rhs.fOriginalExif)) , fIPTCBlock (CloneAutoPtr (rhs.fIPTCBlock, allocator)) , fIPTCOffset (rhs.fIPTCOffset) #if qDNGUseXMP , fXMP (CloneAutoPtr (rhs.fXMP)) #endif , fEmbeddedXMPDigest (rhs.fEmbeddedXMPDigest) , fXMPinSidecar (rhs.fXMPinSidecar) , fXMPisNewer (rhs.fXMPisNewer) , fSourceMIMI (rhs.fSourceMIMI) { } /******************************************************************************/ dng_metadata * dng_metadata::Clone (dng_memory_allocator &allocator) const { return new dng_metadata (*this, allocator); } /******************************************************************************/ void dng_metadata::SetBaseOrientation (const dng_orientation &orientation) { fHasBaseOrientation = true; fBaseOrientation = orientation; } /******************************************************************************/ void dng_metadata::ApplyOrientation (const dng_orientation &orientation) { fBaseOrientation += orientation; #if qDNGUseXMP fXMP->SetOrientation (fBaseOrientation); #endif } /*****************************************************************************/ void dng_metadata::ResetExif (dng_exif * newExif) { fExif.Reset (newExif); } /******************************************************************************/ dng_memory_block * dng_metadata::BuildExifBlock (dng_memory_allocator &allocator, const dng_resolution *resolution, bool includeIPTC, const dng_jpeg_preview *thumbnail) const { dng_memory_stream stream (allocator); { // Create the main IFD dng_tiff_directory mainIFD; // Optionally include the resolution tags. 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); } // Optionally include IPTC block. tag_iptc tagIPTC (IPTCData (), IPTCLength ()); if (includeIPTC && tagIPTC.Count ()) { mainIFD.Add (&tagIPTC); } // Exif tags. exif_tag_set exifSet (mainIFD, *GetExif (), IsMakerNoteSafe (), MakerNoteData (), MakerNoteLength (), false); // Figure out the Exif IFD offset. uint32 exifOffset = 8 + mainIFD.Size (); exifSet.Locate (exifOffset); // Thumbnail IFD (if any). dng_tiff_directory thumbIFD; tag_uint16 thumbCompression (tcCompression, ccOldJPEG); tag_urational thumbXResolution (tcXResolution, dng_urational (72, 1)); tag_urational thumbYResolution (tcYResolution, dng_urational (72, 1)); tag_uint16 thumbResolutionUnit (tcResolutionUnit, ruInch); tag_uint32 thumbDataOffset (tcJPEGInterchangeFormat , 0); tag_uint32 thumbDataLength (tcJPEGInterchangeFormatLength, 0); if (thumbnail) { thumbIFD.Add (&thumbCompression); thumbIFD.Add (&thumbXResolution); thumbIFD.Add (&thumbYResolution); thumbIFD.Add (&thumbResolutionUnit); thumbIFD.Add (&thumbDataOffset); thumbIFD.Add (&thumbDataLength); thumbDataLength.Set (thumbnail->fCompressedData->LogicalSize ()); uint32 thumbOffset = exifOffset + exifSet.Size (); mainIFD.SetChained (thumbOffset); thumbDataOffset.Set (thumbOffset + thumbIFD.Size ()); } // Don't write anything unless the main IFD has some tags. if (mainIFD.Size () != 0) { // 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); exifSet.Put (stream); if (thumbnail) { thumbIFD.Put (stream); stream.Put (thumbnail->fCompressedData->Buffer (), thumbnail->fCompressedData->LogicalSize ()); } // Trim the file to this length. stream.Flush (); stream.SetLength (stream.Position ()); } } return stream.AsMemoryBlock (allocator); } /******************************************************************************/ void dng_metadata::SetIPTC (AutoPtr &block, uint64 offset) { fIPTCBlock.Reset (block.Release ()); fIPTCOffset = offset; } /******************************************************************************/ void dng_metadata::SetIPTC (AutoPtr &block) { SetIPTC (block, kDNGStreamInvalidOffset); } /******************************************************************************/ void dng_metadata::ClearIPTC () { fIPTCBlock.Reset (); fIPTCOffset = kDNGStreamInvalidOffset; } /*****************************************************************************/ const void * dng_metadata::IPTCData () const { if (fIPTCBlock.Get ()) { return fIPTCBlock->Buffer (); } return NULL; } /*****************************************************************************/ uint32 dng_metadata::IPTCLength () const { if (fIPTCBlock.Get ()) { return fIPTCBlock->LogicalSize (); } return 0; } /*****************************************************************************/ uint64 dng_metadata::IPTCOffset () const { if (fIPTCBlock.Get ()) { return fIPTCOffset; } return kDNGStreamInvalidOffset; } /*****************************************************************************/ dng_fingerprint dng_metadata::IPTCDigest (bool includePadding) const { if (IPTCLength ()) { dng_md5_printer printer; const uint8 *data = (const uint8 *) IPTCData (); uint32 count = IPTCLength (); // Because of some stupid ways of storing the IPTC data, the IPTC // data might be padded with up to three zeros. The official Adobe // logic is to include these zeros in the digest. However, older // versions of the Camera Raw code did not include the padding zeros // in the digest, so we support both methods and allow either to // match. if (!includePadding) { uint32 removed = 0; while ((removed < 3) && (count > 0) && (data [count - 1] == 0)) { removed++; count--; } } printer.Process (data, count); return printer.Result (); } return dng_fingerprint (); } /******************************************************************************/ #if qDNGUseXMP void dng_metadata::RebuildIPTC (dng_memory_allocator &allocator, bool padForTIFF) { ClearIPTC (); fXMP->RebuildIPTC (*this, allocator, padForTIFF); dng_fingerprint digest = IPTCDigest (); fXMP->SetIPTCDigest (digest); } /*****************************************************************************/ void dng_metadata::ResetXMP (dng_xmp * newXMP) { fXMP.Reset (newXMP); } /*****************************************************************************/ void dng_metadata::ResetXMPSidecarNewer (dng_xmp * newXMP, bool inSidecar, bool isNewer ) { fXMP.Reset (newXMP); fXMPinSidecar = inSidecar; fXMPisNewer = isNewer; } /*****************************************************************************/ bool dng_metadata::SetXMP (dng_host &host, const void *buffer, uint32 count, bool xmpInSidecar, bool xmpIsNewer) { bool result = false; try { AutoPtr tempXMP (host.Make_dng_xmp ()); tempXMP->Parse (host, buffer, count); ResetXMPSidecarNewer (tempXMP.Release (), xmpInSidecar, xmpIsNewer); result = true; } catch (dng_exception &except) { // Don't ignore transient errors. if (host.IsTransientError (except.ErrorCode ())) { throw; } // Eat other parsing errors. } catch (...) { // Eat unknown parsing exceptions. } return result; } /*****************************************************************************/ void dng_metadata::SetEmbeddedXMP (dng_host &host, const void *buffer, uint32 count) { if (SetXMP (host, buffer, count)) { dng_md5_printer printer; printer.Process (buffer, count); fEmbeddedXMPDigest = printer.Result (); // Remove any sidecar specific tags from embedded XMP. if (fXMP.Get ()) { fXMP->Remove (XMP_NS_PHOTOSHOP, "SidecarForExtension"); fXMP->Remove (XMP_NS_PHOTOSHOP, "EmbeddedXMPDigest"); } } else { fEmbeddedXMPDigest.Clear (); } } #endif /*****************************************************************************/ void dng_metadata::SynchronizeMetadata () { if (!fOriginalExif.Get ()) { fOriginalExif.Reset (fExif->Clone ()); } #if qDNGUseXMP fXMP->ValidateMetadata (); fXMP->IngestIPTC (*this, fXMPisNewer); fXMP->SyncExif (*fExif.Get ()); fXMP->SyncOrientation (*this, fXMPinSidecar); #endif } /*****************************************************************************/ void dng_metadata::UpdateDateTime (const dng_date_time_info &dt) { fExif->UpdateDateTime (dt); #if qDNGUseXMP fXMP->UpdateDateTime (dt); #endif } /*****************************************************************************/ void dng_metadata::UpdateDateTimeToNow () { dng_date_time_info dt; CurrentDateTimeAndZone (dt); UpdateDateTime (dt); #if qDNGUseXMP fXMP->UpdateMetadataDate (dt); #endif } /*****************************************************************************/ void dng_metadata::UpdateMetadataDateTimeToNow () { dng_date_time_info dt; CurrentDateTimeAndZone (dt); #if qDNGUseXMP fXMP->UpdateMetadataDate (dt); #endif } /*****************************************************************************/ dng_negative::dng_negative (dng_host &host) : fAllocator (host.Allocator ()) , fModelName () , fLocalName () , fDefaultCropSizeH () , fDefaultCropSizeV () , fDefaultCropOriginH (0, 1) , fDefaultCropOriginV (0, 1) , fDefaultUserCropT (0, 1) , fDefaultUserCropL (0, 1) , fDefaultUserCropB (1, 1) , fDefaultUserCropR (1, 1) , fDefaultScaleH (1, 1) , fDefaultScaleV (1, 1) , fBestQualityScale (1, 1) , fOriginalDefaultFinalSize () , fOriginalBestQualityFinalSize () , fOriginalDefaultCropSizeH () , fOriginalDefaultCropSizeV () , fRawToFullScaleH (1.0) , fRawToFullScaleV (1.0) , fBaselineNoise (100, 100) , fNoiseReductionApplied (0, 0) , fNoiseProfile () , fBaselineExposure ( 0, 100) , fBaselineSharpness (100, 100) , fChromaBlurRadius () , fAntiAliasStrength (100, 100) , fLinearResponseLimit (100, 100) , fShadowScale (1, 1) , fColorimetricReference (crSceneReferred) , fColorChannels (0) , fAnalogBalance () , fCameraNeutral () , fCameraWhiteXY () , fCameraCalibration1 () , fCameraCalibration2 () , fCameraCalibrationSignature () , fCameraProfile () , fAsShotProfileName () , fRawImageDigest () , fNewRawImageDigest () , fRawDataUniqueID () , fOriginalRawFileName () , fHasOriginalRawFileData (false) , fOriginalRawFileData () , fOriginalRawFileDigest () , fDNGPrivateData () , fMetadata (host) , fLinearizationInfo () , fMosaicInfo () , fOpcodeList1 (1) , fOpcodeList2 (2) , fOpcodeList3 (3) , fStage1Image () , fStage2Image () , fStage3Image () , fStage3Gain (1.0) , fIsPreview (false) , fIsDamaged (false) , fRawImageStage (rawImageStageNone) , fRawImage () , fRawFloatBitDepth (0) , fRawJPEGImage () , fRawJPEGImageDigest () , fTransparencyMask () , fRawTransparencyMask () , fRawTransparencyMaskBitDepth (0) , fUnflattenedStage3Image () { } /*****************************************************************************/ dng_negative::~dng_negative () { // Delete any camera profiles owned by this negative. ClearProfiles (); } /******************************************************************************/ void dng_negative::Initialize () { } /******************************************************************************/ dng_negative * dng_negative::Make (dng_host &host) { AutoPtr result (new dng_negative (host)); if (!result.Get ()) { ThrowMemoryFull (); } result->Initialize (); return result.Release (); } /******************************************************************************/ dng_metadata * dng_negative::CloneInternalMetadata () const { return InternalMetadata ().Clone (Allocator ()); } /******************************************************************************/ dng_orientation dng_negative::ComputeOrientation (const dng_metadata &metadata) const { return metadata.BaseOrientation (); } /******************************************************************************/ void dng_negative::SetAnalogBalance (const dng_vector &b) { real64 minEntry = b.MinEntry (); if (b.NotEmpty () && minEntry > 0.0) { fAnalogBalance = b; fAnalogBalance.Scale (1.0 / minEntry); fAnalogBalance.Round (1000000.0); } else { fAnalogBalance.Clear (); } } /*****************************************************************************/ real64 dng_negative::AnalogBalance (uint32 channel) const { DNG_ASSERT (channel < ColorChannels (), "Channel out of bounds"); if (channel < fAnalogBalance.Count ()) { return fAnalogBalance [channel]; } return 1.0; } /*****************************************************************************/ dng_urational dng_negative::AnalogBalanceR (uint32 channel) const { dng_urational result; result.Set_real64 (AnalogBalance (channel), 1000000); return result; } /******************************************************************************/ void dng_negative::SetCameraNeutral (const dng_vector &n) { real64 maxEntry = n.MaxEntry (); if (n.NotEmpty () && maxEntry > 0.0) { fCameraNeutral = n; fCameraNeutral.Scale (1.0 / maxEntry); fCameraNeutral.Round (1000000.0); } else { fCameraNeutral.Clear (); } } /*****************************************************************************/ dng_urational dng_negative::CameraNeutralR (uint32 channel) const { dng_urational result; result.Set_real64 (CameraNeutral () [channel], 1000000); return result; } /******************************************************************************/ void dng_negative::SetCameraWhiteXY (const dng_xy_coord &coord) { if (coord.IsValid ()) { fCameraWhiteXY.x = Round_int32 (coord.x * 1000000.0) / 1000000.0; fCameraWhiteXY.y = Round_int32 (coord.y * 1000000.0) / 1000000.0; } else { fCameraWhiteXY.Clear (); } } /*****************************************************************************/ const dng_xy_coord & dng_negative::CameraWhiteXY () const { DNG_ASSERT (HasCameraWhiteXY (), "Using undefined CameraWhiteXY"); return fCameraWhiteXY; } /*****************************************************************************/ void dng_negative::GetCameraWhiteXY (dng_urational &x, dng_urational &y) const { dng_xy_coord coord = CameraWhiteXY (); x.Set_real64 (coord.x, 1000000); y.Set_real64 (coord.y, 1000000); } /*****************************************************************************/ void dng_negative::SetCameraCalibration1 (const dng_matrix &m) { fCameraCalibration1 = m; fCameraCalibration1.Round (10000); } /******************************************************************************/ void dng_negative::SetCameraCalibration2 (const dng_matrix &m) { fCameraCalibration2 = m; fCameraCalibration2.Round (10000); } /******************************************************************************/ void dng_negative::AddProfile (AutoPtr &profile) { // Make sure we have a profile to add. if (!profile.Get ()) { return; } // We must have some profile name. Use "embedded" if nothing else. if (profile->Name ().IsEmpty ()) { profile->SetName (kProfileName_Embedded); } // Special case support for reading older DNG files which did not store // the profile name in the main IFD profile. if (fCameraProfile.size ()) { // See the first profile has a default "embedded" name, and has // the same data as the profile we are adding. if (fCameraProfile [0]->NameIsEmbedded () && fCameraProfile [0]->EqualData (*profile.Get ())) { // If the profile we are deleting was read from DNG // then the new profile should be marked as such also. if (fCameraProfile [0]->WasReadFromDNG ()) { profile->SetWasReadFromDNG (); } // If the profile we are deleting wasn't read from disk then the new // profile should be marked as such also. if (!fCameraProfile [0]->WasReadFromDisk ()) { profile->SetWasReadFromDisk (false); } // Delete the profile with default name. delete fCameraProfile [0]; fCameraProfile [0] = NULL; fCameraProfile.erase (fCameraProfile.begin ()); } } // Duplicate detection logic. We give a preference to last added profile // so the profiles end up in a more consistent order no matter what profiles // happen to be embedded in the DNG. for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { // Instead of checking for matching fingerprints, we check that the two // profiles have the same color and have the same name. This allows two // profiles that are identical except for copyright string and embed policy // to be considered duplicates. const bool equalColorAndSameName = (fCameraProfile [index]->EqualData (*profile.Get ()) && fCameraProfile [index]->Name () == profile->Name ()); if (equalColorAndSameName) { // If the profile we are deleting was read from DNG // then the new profile should be marked as such also. if (fCameraProfile [index]->WasReadFromDNG ()) { profile->SetWasReadFromDNG (); } // If the profile we are deleting wasn't read from disk then the new // profile should be marked as such also. if (!fCameraProfile [index]->WasReadFromDisk ()) { profile->SetWasReadFromDisk (false); } // Delete the duplicate profile. delete fCameraProfile [index]; fCameraProfile [index] = NULL; fCameraProfile.erase (fCameraProfile.begin () + index); break; } } // Now add to profile list. fCameraProfile.push_back (NULL); fCameraProfile [fCameraProfile.size () - 1] = profile.Release (); } /******************************************************************************/ void dng_negative::ClearProfiles () { // Delete any camera profiles owned by this negative. for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { if (fCameraProfile [index]) { delete fCameraProfile [index]; fCameraProfile [index] = NULL; } } // Now empty list. fCameraProfile.clear (); } /*****************************************************************************/ void dng_negative::ClearProfiles (bool clearBuiltinMatrixProfiles, bool clearReadFromDisk) { // If neither flag is set, then there's nothing to do. if (!clearBuiltinMatrixProfiles && !clearReadFromDisk) { return; } // Delete any camera profiles in this negative that match the specified criteria. dng_std_vector::iterator iter = fCameraProfile.begin (); dng_std_vector::iterator next; for (; iter != fCameraProfile.end (); iter = next) { dng_camera_profile *profile = *iter; // If the profile is invalid (i.e., NULL pointer), or meets one of the // specified criteria, then axe it. if (!profile || (clearBuiltinMatrixProfiles && profile->WasBuiltinMatrix ()) || (clearReadFromDisk && profile->WasReadFromDisk ())) { delete profile; next = fCameraProfile.erase (iter); } // Otherwise, just advance to the next element. else { next = iter + 1; } } } /******************************************************************************/ uint32 dng_negative::ProfileCount () const { return (uint32) fCameraProfile.size (); } /******************************************************************************/ const dng_camera_profile & dng_negative::ProfileByIndex (uint32 index) const { DNG_ASSERT (index < ProfileCount (), "Invalid index for ProfileByIndex"); return *fCameraProfile [index]; } /*****************************************************************************/ const dng_camera_profile * dng_negative::ProfileByID (const dng_camera_profile_id &id, bool useDefaultIfNoMatch) const { uint32 index; // If this negative does not have any profiles, we are not going to // find a match. uint32 profileCount = ProfileCount (); if (profileCount == 0) { return NULL; } // If we have both a profile name and fingerprint, try matching both. if (id.Name ().NotEmpty () && id.Fingerprint ().IsValid ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Name () == profile.Name () && id.Fingerprint () == profile.Fingerprint ()) { return &profile; } } } // If we have a name, try matching that. if (id.Name ().NotEmpty ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Name () == profile.Name ()) { return &profile; } } } // If we have a valid fingerprint, try matching that. if (id.Fingerprint ().IsValid ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Fingerprint () == profile.Fingerprint ()) { return &profile; } } } // Try "upgrading" profile name versions. if (id.Name ().NotEmpty ()) { dng_string baseName; int32 version; SplitCameraProfileName (id.Name (), baseName, version); int32 bestIndex = -1; int32 bestVersion = 0; for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (profile.Name ().StartsWith (baseName.Get ())) { dng_string testBaseName; int32 testVersion; SplitCameraProfileName (profile.Name (), testBaseName, testVersion); if (bestIndex == -1 || testVersion > bestVersion) { bestIndex = index; bestVersion = testVersion; } } } if (bestIndex != -1) { return &ProfileByIndex (bestIndex); } } // Did not find a match any way. See if we should return a default value. if (useDefaultIfNoMatch) { return &ProfileByIndex (0); } // Found nothing. return NULL; } /*****************************************************************************/ const dng_camera_profile * dng_negative::ComputeCameraProfileToEmbed (const dng_metadata & /* metadata */) const { uint32 index; uint32 count = ProfileCount (); if (count == 0) { return NULL; } // First try to look for the first profile that was already in the DNG // when we read it. for (index = 0; index < count; index++) { const dng_camera_profile &profile (ProfileByIndex (index)); if (profile.WasReadFromDNG ()) { return &profile; } } // Next we look for the first profile that is legal to embed. for (index = 0; index < count; index++) { const dng_camera_profile &profile (ProfileByIndex (index)); if (profile.IsLegalToEmbed ()) { return &profile; } } // Else just return the first profile. return fCameraProfile [0]; } /*****************************************************************************/ dng_color_spec * dng_negative::MakeColorSpec (const dng_camera_profile_id &id) const { dng_color_spec *spec = new dng_color_spec (*this, ProfileByID (id)); if (!spec) { ThrowMemoryFull (); } return spec; } /*****************************************************************************/ dng_fingerprint dng_negative::FindImageDigest (dng_host &host, const dng_image &image) const { dng_md5_printer printer; dng_pixel_buffer buffer (image.Bounds (), 0, image.Planes (), image.PixelType (), pcInterleaved, NULL); // Sometimes we expand 8-bit data to 16-bit data while reading or // writing, so always compute the digest of 8-bit data as 16-bits. if (buffer.fPixelType == ttByte) { buffer.fPixelType = ttShort; buffer.fPixelSize = 2; } const uint32 kBufferRows = 16; uint32 bufferBytes = 0; if (!SafeUint32Mult (kBufferRows, buffer.fRowStep, &bufferBytes) || !SafeUint32Mult (bufferBytes, buffer.fPixelSize, &bufferBytes)) { ThrowMemoryFull("Arithmetic overflow computing buffer size."); } AutoPtr bufferData (host.Allocate (bufferBytes)); buffer.fData = bufferData->Buffer (); dng_rect area; dng_tile_iterator iter (dng_point (kBufferRows, image.Width ()), image.Bounds ()); while (iter.GetOneTile (area)) { host.SniffForAbort (); buffer.fArea = area; image.Get (buffer); uint32 count = buffer.fArea.H () * buffer.fRowStep * buffer.fPixelSize; #if qDNGBigEndian // We need to use the same byte order to compute // the digest, no matter the native order. Little-endian // is more common now, so use that. switch (buffer.fPixelSize) { case 1: break; case 2: { DoSwapBytes16 ((uint16 *) buffer.fData, count >> 1); break; } case 4: { DoSwapBytes32 ((uint32 *) buffer.fData, count >> 2); break; } default: { DNG_REPORT ("Unexpected pixel size"); break; } } #endif printer.Process (buffer.fData, count); } return printer.Result (); } /*****************************************************************************/ void dng_negative::FindRawImageDigest (dng_host &host) const { if (fRawImageDigest.IsNull ()) { // Since we are adding the floating point and transparency support // in DNG 1.4, and there are no legacy floating point or transparent // DNGs, switch to using the more MP friendly algorithm to compute // the digest for these images. if (RawImage ().PixelType () == ttFloat || RawTransparencyMask ()) { FindNewRawImageDigest (host); fRawImageDigest = fNewRawImageDigest; } else { #if qDNGValidate dng_timer timeScope ("FindRawImageDigest time"); #endif fRawImageDigest = FindImageDigest (host, RawImage ()); } } } /*****************************************************************************/ class dng_find_new_raw_image_digest_task : public dng_area_task { private: enum { kTileSize = 256 }; const dng_image &fImage; uint32 fPixelType; uint32 fPixelSize; uint32 fTilesAcross; uint32 fTilesDown; uint32 fTileCount; AutoArray fTileHash; AutoPtr fBufferData [kMaxMPThreads]; public: dng_find_new_raw_image_digest_task (const dng_image &image, uint32 pixelType) : fImage (image) , fPixelType (pixelType) , fPixelSize (TagTypeSize (pixelType)) , fTilesAcross (0) , fTilesDown (0) , fTileCount (0) , fTileHash () { fMinTaskArea = 1; fUnitCell = dng_point (Min_int32 (kTileSize, fImage.Bounds ().H ()), Min_int32 (kTileSize, fImage.Bounds ().W ())); fMaxTileSize = fUnitCell; } virtual void Start (uint32 threadCount, const dng_point &tileSize, dng_memory_allocator *allocator, dng_abort_sniffer * /* sniffer */) { if (tileSize != fUnitCell) { ThrowProgramError (); } fTilesAcross = (fImage.Bounds ().W () + fUnitCell.h - 1) / fUnitCell.h; fTilesDown = (fImage.Bounds ().H () + fUnitCell.v - 1) / fUnitCell.v; fTileCount = fTilesAcross * fTilesDown; fTileHash.Reset (fTileCount); const uint32 bufferSize = ComputeBufferSize(fPixelType, tileSize, fImage.Planes(), padNone); for (uint32 index = 0; index < threadCount; index++) { fBufferData [index].Reset (allocator->Allocate (bufferSize)); } } virtual void Process (uint32 threadIndex, const dng_rect &tile, dng_abort_sniffer * /* sniffer */) { int32 colIndex = (tile.l - fImage.Bounds ().l) / fUnitCell.h; int32 rowIndex = (tile.t - fImage.Bounds ().t) / fUnitCell.v; DNG_ASSERT (tile.l == fImage.Bounds ().l + colIndex * fUnitCell.h && tile.t == fImage.Bounds ().t + rowIndex * fUnitCell.v, "Bad tile origin"); uint32 tileIndex = rowIndex * fTilesAcross + colIndex; dng_pixel_buffer buffer (tile, 0, fImage.Planes (), fPixelType, pcPlanar, fBufferData [threadIndex]->Buffer ()); fImage.Get (buffer); uint32 count = buffer.fPlaneStep * buffer.fPlanes * buffer.fPixelSize; #if qDNGBigEndian // We need to use the same byte order to compute // the digest, no matter the native order. Little-endian // is more common now, so use that. switch (buffer.fPixelSize) { case 1: break; case 2: { DoSwapBytes16 ((uint16 *) buffer.fData, count >> 1); break; } case 4: { DoSwapBytes32 ((uint32 *) buffer.fData, count >> 2); break; } default: { DNG_REPORT ("Unexpected pixel size"); break; } } #endif dng_md5_printer printer; printer.Process (buffer.fData, count); fTileHash [tileIndex] = printer.Result (); } dng_fingerprint Result () { dng_md5_printer printer; for (uint32 tileIndex = 0; tileIndex < fTileCount; tileIndex++) { printer.Process (fTileHash [tileIndex] . data, 16); } return printer.Result (); } }; /*****************************************************************************/ void dng_negative::FindNewRawImageDigest (dng_host &host) const { if (fNewRawImageDigest.IsNull ()) { #if qDNGValidate dng_timer timeScope ("FindNewRawImageDigest time"); #endif // Find fast digest of the raw image. { const dng_image &rawImage = RawImage (); // Find pixel type that will be saved in the file. When saving DNGs, we convert // some 16-bit data to 8-bit data, so we need to do the matching logic here. 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 = GetLinearizationInfo (); if (rangeInfo) { if (rangeInfo->fLinearizationTable.Get ()) { uint32 entries = rangeInfo->fLinearizationTable->LogicalSize () >> 1; if (entries <= 256) { rawPixelType = ttByte; } } } } // Find the fast digest on the raw image. dng_find_new_raw_image_digest_task task (rawImage, rawPixelType); host.PerformAreaTask (task, rawImage.Bounds ()); fNewRawImageDigest = task.Result (); } // If there is a transparancy mask, we need to include that in the // digest also. if (RawTransparencyMask () != NULL) { // Find the fast digest on the raw mask. dng_fingerprint maskDigest; { dng_find_new_raw_image_digest_task task (*RawTransparencyMask (), RawTransparencyMask ()->PixelType ()); host.PerformAreaTask (task, RawTransparencyMask ()->Bounds ()); maskDigest = task.Result (); } // Combine the two digests into a single digest. dng_md5_printer printer; printer.Process (fNewRawImageDigest.data, 16); printer.Process (maskDigest.data, 16); fNewRawImageDigest = printer.Result (); } } } /*****************************************************************************/ void dng_negative::ValidateRawImageDigest (dng_host &host) { if (Stage1Image () && !IsPreview () && (fRawImageDigest .IsValid () || fNewRawImageDigest.IsValid ())) { bool isNewDigest = fNewRawImageDigest.IsValid (); dng_fingerprint &rawDigest = isNewDigest ? fNewRawImageDigest : fRawImageDigest; // For lossy compressed JPEG images, we need to compare the stored // digest to the digest computed from the compressed data, since // decompressing lossy JPEG data is itself a lossy process. if (RawJPEGImageDigest ().IsValid () || RawJPEGImage ()) { // Compute the raw JPEG image digest if we have not done so // already. FindRawJPEGImageDigest (host); if (rawDigest != RawJPEGImageDigest ()) { #if qDNGValidate ReportError ("RawImageDigest does not match raw jpeg image"); #else SetIsDamaged (true); #endif } } // Else we can compare the stored digest to the image in memory. else { dng_fingerprint oldDigest = rawDigest; try { rawDigest.Clear (); if (isNewDigest) { FindNewRawImageDigest (host); } else { FindRawImageDigest (host); } } catch (...) { rawDigest = oldDigest; throw; } if (oldDigest != rawDigest) { #if qDNGValidate if (isNewDigest) { ReportError ("NewRawImageDigest does not match raw image"); } else { ReportError ("RawImageDigest does not match raw image"); } SetIsDamaged (true); #else if (!isNewDigest) { // Note that Lightroom 1.4 Windows had a bug that corrupts the // first four bytes of the RawImageDigest tag. So if the last // twelve bytes match, this is very likely the result of the // bug, and not an actual corrupt file. So don't report this // to the user--just fix it. { bool matchLast12 = true; for (uint32 j = 4; j < 16; j++) { matchLast12 = matchLast12 && (oldDigest.data [j] == fRawImageDigest.data [j]); } if (matchLast12) { return; } } // Sometimes Lightroom 1.4 would corrupt more than the first four // bytes, but for all those files that I have seen so far the // resulting first four bytes are 0x08 0x00 0x00 0x00. if (oldDigest.data [0] == 0x08 && oldDigest.data [1] == 0x00 && oldDigest.data [2] == 0x00 && oldDigest.data [3] == 0x00) { return; } } SetIsDamaged (true); #endif } } } } /*****************************************************************************/ // If the raw data unique ID is missing, compute one based on a MD5 hash of // the raw image hash and the model name, plus other commonly changed // data that can affect rendering. void dng_negative::FindRawDataUniqueID (dng_host &host) const { if (fRawDataUniqueID.IsNull ()) { dng_md5_printer_stream printer; // If we have a raw jpeg image, it is much faster to // use its digest as part of the unique ID since // the data size is much smaller. We cannot use it // if there a transparency mask, since that is not // included in the RawJPEGImageDigest. if (RawJPEGImage () && !RawTransparencyMask ()) { FindRawJPEGImageDigest (host); printer.Put (fRawJPEGImageDigest.data, 16); } // Include the new raw image digest in the unique ID. else { FindNewRawImageDigest (host); printer.Put (fNewRawImageDigest.data, 16); } // Include model name. printer.Put (ModelName ().Get (), ModelName ().Length ()); // Include default crop area, since DNG Recover Edges can modify // these values and they affect rendering. printer.Put_uint32 (fDefaultCropSizeH.n); printer.Put_uint32 (fDefaultCropSizeH.d); printer.Put_uint32 (fDefaultCropSizeV.n); printer.Put_uint32 (fDefaultCropSizeV.d); printer.Put_uint32 (fDefaultCropOriginH.n); printer.Put_uint32 (fDefaultCropOriginH.d); printer.Put_uint32 (fDefaultCropOriginV.n); printer.Put_uint32 (fDefaultCropOriginV.d); // Include default user crop. printer.Put_uint32 (fDefaultUserCropT.n); printer.Put_uint32 (fDefaultUserCropT.d); printer.Put_uint32 (fDefaultUserCropL.n); printer.Put_uint32 (fDefaultUserCropL.d); printer.Put_uint32 (fDefaultUserCropB.n); printer.Put_uint32 (fDefaultUserCropB.d); printer.Put_uint32 (fDefaultUserCropR.n); printer.Put_uint32 (fDefaultUserCropR.d); // Include opcode lists, since lens correction utilities can modify // these values and they affect rendering. fOpcodeList1.FingerprintToStream (printer); fOpcodeList2.FingerprintToStream (printer); fOpcodeList3.FingerprintToStream (printer); fRawDataUniqueID = printer.Result (); } } /******************************************************************************/ // Forces recomputation of RawDataUniqueID, useful to call // after modifying the opcode lists, etc. void dng_negative::RecomputeRawDataUniqueID (dng_host &host) { fRawDataUniqueID.Clear (); FindRawDataUniqueID (host); } /******************************************************************************/ void dng_negative::FindOriginalRawFileDigest () const { if (fOriginalRawFileDigest.IsNull () && fOriginalRawFileData.Get ()) { dng_md5_printer printer; printer.Process (fOriginalRawFileData->Buffer (), fOriginalRawFileData->LogicalSize ()); fOriginalRawFileDigest = printer.Result (); } } /*****************************************************************************/ void dng_negative::ValidateOriginalRawFileDigest () { if (fOriginalRawFileDigest.IsValid () && fOriginalRawFileData.Get ()) { dng_fingerprint oldDigest = fOriginalRawFileDigest; try { fOriginalRawFileDigest.Clear (); FindOriginalRawFileDigest (); } catch (...) { fOriginalRawFileDigest = oldDigest; throw; } if (oldDigest != fOriginalRawFileDigest) { #if qDNGValidate ReportError ("OriginalRawFileDigest does not match OriginalRawFileData"); #else SetIsDamaged (true); #endif // Don't "repair" the original image data digest. Once it is // bad, it stays bad. The user cannot tell by looking at the image // whether the damage is acceptable and can be ignored in the // future. fOriginalRawFileDigest = oldDigest; } } } /******************************************************************************/ dng_rect dng_negative::DefaultCropArea () const { // First compute the area using simple rounding. dng_rect result; result.l = Round_int32 (fDefaultCropOriginH.As_real64 () * fRawToFullScaleH); result.t = Round_int32 (fDefaultCropOriginV.As_real64 () * fRawToFullScaleV); result.r = result.l + Round_int32 (fDefaultCropSizeH.As_real64 () * fRawToFullScaleH); result.b = result.t + Round_int32 (fDefaultCropSizeV.As_real64 () * fRawToFullScaleV); // Sometimes the simple rounding causes the resulting default crop // area to slide off the scaled image area. So we force this not // to happen. We only do this if the image is not stubbed. const dng_image *image = Stage3Image (); if (image) { dng_point imageSize = image->Size (); if (result.r > imageSize.h) { result.l -= result.r - imageSize.h; result.r = imageSize.h; } if (result.b > imageSize.v) { result.t -= result.b - imageSize.v; result.b = imageSize.v; } } return result; } /*****************************************************************************/ real64 dng_negative::TotalBaselineExposure (const dng_camera_profile_id &profileID) const { real64 total = BaselineExposure (); const dng_camera_profile *profile = ProfileByID (profileID); if (profile) { real64 offset = profile->BaselineExposureOffset ().As_real64 (); total += offset; } return total; } /******************************************************************************/ void dng_negative::SetShadowScale (const dng_urational &scale) { if (scale.d > 0) { real64 s = scale.As_real64 (); if (s > 0.0 && s <= 1.0) { fShadowScale = scale; } } } /******************************************************************************/ void dng_negative::SetActiveArea (const dng_rect &area) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fActiveArea = area; } /******************************************************************************/ void dng_negative::SetMaskedAreas (uint32 count, const dng_rect *area) { DNG_ASSERT (count <= kMaxMaskedAreas, "Too many masked areas"); NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fMaskedAreaCount = Min_uint32 (count, kMaxMaskedAreas); for (uint32 index = 0; index < info.fMaskedAreaCount; index++) { info.fMaskedArea [index] = area [index]; } } /*****************************************************************************/ void dng_negative::SetLinearization (AutoPtr &curve) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fLinearizationTable.Reset (curve.Release ()); } /*****************************************************************************/ void dng_negative::SetBlackLevel (real64 black, int32 plane) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackLevelRepeatRows = 1; info.fBlackLevelRepeatCols = 1; if (plane < 0) { for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fBlackLevel [0] [0] [j] = black; } } else { info.fBlackLevel [0] [0] [plane] = black; } info.RoundBlacks (); } /*****************************************************************************/ void dng_negative::SetQuadBlacks (real64 black0, real64 black1, real64 black2, real64 black3, int32 plane) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackLevelRepeatRows = 2; info.fBlackLevelRepeatCols = 2; if (plane < 0) { for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fBlackLevel [0] [0] [j] = black0; info.fBlackLevel [0] [1] [j] = black1; info.fBlackLevel [1] [0] [j] = black2; info.fBlackLevel [1] [1] [j] = black3; } } else { info.fBlackLevel [0] [0] [plane] = black0; info.fBlackLevel [0] [1] [plane] = black1; info.fBlackLevel [1] [0] [plane] = black2; info.fBlackLevel [1] [1] [plane] = black3; } info.RoundBlacks (); } /*****************************************************************************/ void dng_negative::SetRowBlacks (const real64 *blacks, uint32 count) { if (count) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); uint32 byteCount = 0; if (!SafeUint32Mult (count, (uint32) sizeof (real64), &byteCount)) { ThrowMemoryFull("Arithmetic overflow computing byte count."); } info.fBlackDeltaV.Reset (Allocator ().Allocate (byteCount)); DoCopyBytes (blacks, info.fBlackDeltaV->Buffer (), byteCount); info.RoundBlacks (); } else if (fLinearizationInfo.Get ()) { dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackDeltaV.Reset (); } } /*****************************************************************************/ void dng_negative::SetColumnBlacks (const real64 *blacks, uint32 count) { if (count) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); uint32 byteCount = 0; if (!SafeUint32Mult (count, (uint32) sizeof (real64), &byteCount)) { ThrowMemoryFull("Arithmetic overflow computing byte count."); } info.fBlackDeltaH.Reset (Allocator ().Allocate (byteCount)); DoCopyBytes (blacks, info.fBlackDeltaH->Buffer (), byteCount); info.RoundBlacks (); } else if (fLinearizationInfo.Get ()) { dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackDeltaH.Reset (); } } /*****************************************************************************/ uint32 dng_negative::WhiteLevel (uint32 plane) const { if (fLinearizationInfo.Get ()) { const dng_linearization_info &info = *fLinearizationInfo.Get (); return Round_uint32 (info.fWhiteLevel [plane]); } if (RawImage ().PixelType () == ttFloat) { return 1; } return 0x0FFFF; } /*****************************************************************************/ void dng_negative::SetWhiteLevel (uint32 white, int32 plane) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); if (plane < 0) { for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fWhiteLevel [j] = (real64) white; } } else { info.fWhiteLevel [plane] = (real64) white; } } /******************************************************************************/ void dng_negative::SetColorKeys (ColorKeyCode color0, ColorKeyCode color1, ColorKeyCode color2, ColorKeyCode color3) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); info.fCFAPlaneColor [0] = color0; info.fCFAPlaneColor [1] = color1; info.fCFAPlaneColor [2] = color2; info.fCFAPlaneColor [3] = color3; } /******************************************************************************/ void dng_negative::SetBayerMosaic (uint32 phase) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); ColorKeyCode color0 = (ColorKeyCode) info.fCFAPlaneColor [0]; ColorKeyCode color1 = (ColorKeyCode) info.fCFAPlaneColor [1]; ColorKeyCode color2 = (ColorKeyCode) info.fCFAPlaneColor [2]; info.fCFAPatternSize = dng_point (2, 2); switch (phase) { case 0: { info.fCFAPattern [0] [0] = color1; info.fCFAPattern [0] [1] = color0; info.fCFAPattern [1] [0] = color2; info.fCFAPattern [1] [1] = color1; break; } case 1: { info.fCFAPattern [0] [0] = color0; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [1] [0] = color1; info.fCFAPattern [1] [1] = color2; break; } case 2: { info.fCFAPattern [0] [0] = color2; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [1] [0] = color1; info.fCFAPattern [1] [1] = color0; break; } case 3: { info.fCFAPattern [0] [0] = color1; info.fCFAPattern [0] [1] = color2; info.fCFAPattern [1] [0] = color0; info.fCFAPattern [1] [1] = color1; break; } } info.fColorPlanes = 3; info.fCFALayout = 1; } /******************************************************************************/ void dng_negative::SetFujiMosaic (uint32 phase) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); ColorKeyCode color0 = (ColorKeyCode) info.fCFAPlaneColor [0]; ColorKeyCode color1 = (ColorKeyCode) info.fCFAPlaneColor [1]; ColorKeyCode color2 = (ColorKeyCode) info.fCFAPlaneColor [2]; info.fCFAPatternSize = dng_point (2, 4); switch (phase) { case 0: { info.fCFAPattern [0] [0] = color0; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [0] [2] = color2; info.fCFAPattern [0] [3] = color1; info.fCFAPattern [1] [0] = color2; info.fCFAPattern [1] [1] = color1; info.fCFAPattern [1] [2] = color0; info.fCFAPattern [1] [3] = color1; break; } case 1: { info.fCFAPattern [0] [0] = color2; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [0] [2] = color0; info.fCFAPattern [0] [3] = color1; info.fCFAPattern [1] [0] = color0; info.fCFAPattern [1] [1] = color1; info.fCFAPattern [1] [2] = color2; info.fCFAPattern [1] [3] = color1; break; } } info.fColorPlanes = 3; info.fCFALayout = 2; } /*****************************************************************************/ void dng_negative::SetFujiMosaic6x6 (uint32 phase) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); ColorKeyCode color0 = (ColorKeyCode) info.fCFAPlaneColor [0]; ColorKeyCode color1 = (ColorKeyCode) info.fCFAPlaneColor [1]; ColorKeyCode color2 = (ColorKeyCode) info.fCFAPlaneColor [2]; const uint32 patSize = 6; info.fCFAPatternSize = dng_point (patSize, patSize); info.fCFAPattern [0] [0] = color1; info.fCFAPattern [0] [1] = color2; info.fCFAPattern [0] [2] = color1; info.fCFAPattern [0] [3] = color1; info.fCFAPattern [0] [4] = color0; info.fCFAPattern [0] [5] = color1; info.fCFAPattern [1] [0] = color0; info.fCFAPattern [1] [1] = color1; info.fCFAPattern [1] [2] = color0; info.fCFAPattern [1] [3] = color2; info.fCFAPattern [1] [4] = color1; info.fCFAPattern [1] [5] = color2; info.fCFAPattern [2] [0] = color1; info.fCFAPattern [2] [1] = color2; info.fCFAPattern [2] [2] = color1; info.fCFAPattern [2] [3] = color1; info.fCFAPattern [2] [4] = color0; info.fCFAPattern [2] [5] = color1; info.fCFAPattern [3] [0] = color1; info.fCFAPattern [3] [1] = color0; info.fCFAPattern [3] [2] = color1; info.fCFAPattern [3] [3] = color1; info.fCFAPattern [3] [4] = color2; info.fCFAPattern [3] [5] = color1; info.fCFAPattern [4] [0] = color2; info.fCFAPattern [4] [1] = color1; info.fCFAPattern [4] [2] = color2; info.fCFAPattern [4] [3] = color0; info.fCFAPattern [4] [4] = color1; info.fCFAPattern [4] [5] = color0; info.fCFAPattern [5] [0] = color1; info.fCFAPattern [5] [1] = color0; info.fCFAPattern [5] [2] = color1; info.fCFAPattern [5] [3] = color1; info.fCFAPattern [5] [4] = color2; info.fCFAPattern [5] [5] = color1; DNG_REQUIRE (phase >= 0 && phase < patSize * patSize, "Bad phase in SetFujiMosaic6x6."); if (phase > 0) { dng_mosaic_info temp = info; uint32 phaseRow = phase / patSize; uint32 phaseCol = phase - (phaseRow * patSize); for (uint32 dstRow = 0; dstRow < patSize; dstRow++) { uint32 srcRow = (dstRow + phaseRow) % patSize; for (uint32 dstCol = 0; dstCol < patSize; dstCol++) { uint32 srcCol = (dstCol + phaseCol) % patSize; temp.fCFAPattern [dstRow] [dstCol] = info.fCFAPattern [srcRow] [srcCol]; } } info = temp; } info.fColorPlanes = 3; info.fCFALayout = 1; } /******************************************************************************/ void dng_negative::SetQuadMosaic (uint32 pattern) { // The pattern of the four colors is assumed to be repeat at least every two // columns and eight rows. The pattern is encoded as a 32-bit integer, // with every two bits encoding a color, in scan order for two columns and // eight rows (lsb is first). The usual color coding is: // // 0 = Green // 1 = Magenta // 2 = Cyan // 3 = Yellow // // Examples: // // PowerShot 600 uses 0xe1e4e1e4: // // 0 1 2 3 4 5 // 0 G M G M G M // 1 C Y C Y C Y // 2 M G M G M G // 3 C Y C Y C Y // // PowerShot A5 uses 0x1e4e1e4e: // // 0 1 2 3 4 5 // 0 C Y C Y C Y // 1 G M G M G M // 2 C Y C Y C Y // 3 M G M G M G // // PowerShot A50 uses 0x1b4e4b1e: // // 0 1 2 3 4 5 // 0 C Y C Y C Y // 1 M G M G M G // 2 Y C Y C Y C // 3 G M G M G M // 4 C Y C Y C Y // 5 G M G M G M // 6 Y C Y C Y C // 7 M G M G M G // // PowerShot Pro70 uses 0x1e4b4e1b: // // 0 1 2 3 4 5 // 0 Y C Y C Y C // 1 M G M G M G // 2 C Y C Y C Y // 3 G M G M G M // 4 Y C Y C Y C // 5 G M G M G M // 6 C Y C Y C Y // 7 M G M G M G // // PowerShots Pro90 and G1 use 0xb4b4b4b4: // // 0 1 2 3 4 5 // 0 G M G M G M // 1 Y C Y C Y C NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); if (((pattern >> 16) & 0x0FFFF) != (pattern & 0x0FFFF)) { info.fCFAPatternSize = dng_point (8, 2); } else if (((pattern >> 8) & 0x0FF) != (pattern & 0x0FF)) { info.fCFAPatternSize = dng_point (4, 2); } else { info.fCFAPatternSize = dng_point (2, 2); } for (int32 row = 0; row < info.fCFAPatternSize.v; row++) { for (int32 col = 0; col < info.fCFAPatternSize.h; col++) { uint32 index = (pattern >> ((((row << 1) & 14) + (col & 1)) << 1)) & 3; info.fCFAPattern [row] [col] = info.fCFAPlaneColor [index]; } } info.fColorPlanes = 4; info.fCFALayout = 1; } /******************************************************************************/ void dng_negative::SetGreenSplit (uint32 split) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); info.fBayerGreenSplit = split; } /*****************************************************************************/ void dng_negative::Parse (dng_host &host, dng_stream &stream, dng_info &info) { // Shared info. dng_shared &shared = *(info.fShared.Get ()); // Find IFD holding the main raw information. dng_ifd &rawIFD = *info.fIFD [info.fMainIndex].Get (); // Model name. SetModelName (shared.fUniqueCameraModel.Get ()); // Localized model name. SetLocalName (shared.fLocalizedCameraModel.Get ()); // Base orientation. { uint32 orientation = info.fIFD [0]->fOrientation; if (orientation >= 1 && orientation <= 8) { SetBaseOrientation (dng_orientation::TIFFtoDNG (orientation)); } } // Default crop rectangle. SetDefaultCropSize (rawIFD.fDefaultCropSizeH, rawIFD.fDefaultCropSizeV); SetDefaultCropOrigin (rawIFD.fDefaultCropOriginH, rawIFD.fDefaultCropOriginV); // Default user crop rectangle. SetDefaultUserCrop (rawIFD.fDefaultUserCropT, rawIFD.fDefaultUserCropL, rawIFD.fDefaultUserCropB, rawIFD.fDefaultUserCropR); // Default scale. SetDefaultScale (rawIFD.fDefaultScaleH, rawIFD.fDefaultScaleV); // Best quality scale. SetBestQualityScale (rawIFD.fBestQualityScale); // Baseline noise. SetBaselineNoise (shared.fBaselineNoise.As_real64 ()); // NoiseReductionApplied. SetNoiseReductionApplied (shared.fNoiseReductionApplied); // NoiseProfile. SetNoiseProfile (shared.fNoiseProfile); // Baseline exposure. SetBaselineExposure (shared.fBaselineExposure.As_real64 ()); // Baseline sharpness. SetBaselineSharpness (shared.fBaselineSharpness.As_real64 ()); // Chroma blur radius. SetChromaBlurRadius (rawIFD.fChromaBlurRadius); // Anti-alias filter strength. SetAntiAliasStrength (rawIFD.fAntiAliasStrength); // Linear response limit. SetLinearResponseLimit (shared.fLinearResponseLimit.As_real64 ()); // Shadow scale. SetShadowScale (shared.fShadowScale); // Colorimetric reference. SetColorimetricReference (shared.fColorimetricReference); // Color channels. SetColorChannels (shared.fCameraProfile.fColorPlanes); // Analog balance. if (shared.fAnalogBalance.NotEmpty ()) { SetAnalogBalance (shared.fAnalogBalance); } // Camera calibration matrices if (shared.fCameraCalibration1.NotEmpty ()) { SetCameraCalibration1 (shared.fCameraCalibration1); } if (shared.fCameraCalibration2.NotEmpty ()) { SetCameraCalibration2 (shared.fCameraCalibration2); } if (shared.fCameraCalibration1.NotEmpty () || shared.fCameraCalibration2.NotEmpty ()) { SetCameraCalibrationSignature (shared.fCameraCalibrationSignature.Get ()); } // Embedded camera profiles. if (shared.fCameraProfile.fColorPlanes > 1) { if (qDNGValidate || host.NeedsMeta () || host.NeedsImage ()) { // Add profile from main IFD. { AutoPtr profile (new dng_camera_profile ()); dng_camera_profile_info &profileInfo = shared.fCameraProfile; profile->Parse (stream, profileInfo); // The main embedded profile must be valid. if (!profile->IsValid (shared.fCameraProfile.fColorPlanes)) { ThrowBadFormat (); } profile->SetWasReadFromDNG (); AddProfile (profile); } // Extra profiles. for (uint32 index = 0; index < (uint32) shared.fExtraCameraProfiles.size (); index++) { try { AutoPtr profile (new dng_camera_profile ()); dng_camera_profile_info &profileInfo = shared.fExtraCameraProfiles [index]; profile->Parse (stream, profileInfo); if (!profile->IsValid (shared.fCameraProfile.fColorPlanes)) { ThrowBadFormat (); } profile->SetWasReadFromDNG (); AddProfile (profile); } catch (dng_exception &except) { // Don't ignore transient errors. if (host.IsTransientError (except.ErrorCode ())) { throw; } // Eat other parsing errors. #if qDNGValidate ReportWarning ("Unable to parse extra profile"); #endif } } } // As shot profile name. if (shared.fAsShotProfileName.NotEmpty ()) { SetAsShotProfileName (shared.fAsShotProfileName.Get ()); } } // Raw image data digest. if (shared.fRawImageDigest.IsValid ()) { SetRawImageDigest (shared.fRawImageDigest); } // New raw image data digest. if (shared.fNewRawImageDigest.IsValid ()) { SetNewRawImageDigest (shared.fNewRawImageDigest); } // Raw data unique ID. if (shared.fRawDataUniqueID.IsValid ()) { SetRawDataUniqueID (shared.fRawDataUniqueID); } // Original raw file name. if (shared.fOriginalRawFileName.NotEmpty ()) { SetOriginalRawFileName (shared.fOriginalRawFileName.Get ()); } // Original raw file data. if (shared.fOriginalRawFileDataCount) { SetHasOriginalRawFileData (true); if (host.KeepOriginalFile ()) { uint32 count = shared.fOriginalRawFileDataCount; AutoPtr block (host.Allocate (count)); stream.SetReadPosition (shared.fOriginalRawFileDataOffset); stream.Get (block->Buffer (), count); SetOriginalRawFileData (block); SetOriginalRawFileDigest (shared.fOriginalRawFileDigest); ValidateOriginalRawFileDigest (); } } // DNG private data. if (shared.fDNGPrivateDataCount && (host.SaveDNGVersion () != dngVersion_None)) { uint32 length = shared.fDNGPrivateDataCount; AutoPtr block (host.Allocate (length)); stream.SetReadPosition (shared.fDNGPrivateDataOffset); stream.Get (block->Buffer (), length); SetPrivateData (block); } // Hand off EXIF metadata to negative. ResetExif (info.fExif.Release ()); // Parse linearization info. NeedLinearizationInfo (); fLinearizationInfo.Get ()->Parse (host, stream, info); // Parse mosaic info. if (rawIFD.fPhotometricInterpretation == piCFA) { NeedMosaicInfo (); fMosaicInfo.Get ()->Parse (host, stream, info); } // Fill in original sizes. if (shared.fOriginalDefaultFinalSize.h > 0 && shared.fOriginalDefaultFinalSize.v > 0) { SetOriginalDefaultFinalSize (shared.fOriginalDefaultFinalSize); SetOriginalBestQualityFinalSize (shared.fOriginalDefaultFinalSize); SetOriginalDefaultCropSize (dng_urational (shared.fOriginalDefaultFinalSize.h, 1), dng_urational (shared.fOriginalDefaultFinalSize.v, 1)); } if (shared.fOriginalBestQualityFinalSize.h > 0 && shared.fOriginalBestQualityFinalSize.v > 0) { SetOriginalBestQualityFinalSize (shared.fOriginalBestQualityFinalSize); } if (shared.fOriginalDefaultCropSizeH.As_real64 () >= 1.0 && shared.fOriginalDefaultCropSizeV.As_real64 () >= 1.0) { SetOriginalDefaultCropSize (shared.fOriginalDefaultCropSizeH, shared.fOriginalDefaultCropSizeV); } } /*****************************************************************************/ void dng_negative::SetDefaultOriginalSizes () { // Fill in original sizes if we don't have them already. if (OriginalDefaultFinalSize () == dng_point ()) { SetOriginalDefaultFinalSize (dng_point (DefaultFinalHeight (), DefaultFinalWidth ())); } if (OriginalBestQualityFinalSize () == dng_point ()) { SetOriginalBestQualityFinalSize (dng_point (BestQualityFinalHeight (), BestQualityFinalWidth ())); } if (OriginalDefaultCropSizeH ().NotValid () || OriginalDefaultCropSizeV ().NotValid ()) { SetOriginalDefaultCropSize (DefaultCropSizeH (), DefaultCropSizeV ()); } } /*****************************************************************************/ void dng_negative::PostParse (dng_host &host, dng_stream &stream, dng_info &info) { // Shared info. dng_shared &shared = *(info.fShared.Get ()); if (host.NeedsMeta ()) { // Fill in original sizes if we don't have them already. SetDefaultOriginalSizes (); // MakerNote. if (shared.fMakerNoteCount) { // See if we know if the MakerNote is safe or not. SetMakerNoteSafety (shared.fMakerNoteSafety == 1); // If the MakerNote is safe, preserve it as a MakerNote. if (IsMakerNoteSafe ()) { AutoPtr block (host.Allocate (shared.fMakerNoteCount)); stream.SetReadPosition (shared.fMakerNoteOffset); stream.Get (block->Buffer (), shared.fMakerNoteCount); SetMakerNote (block); } } // IPTC metadata. if (shared.fIPTC_NAA_Count) { AutoPtr block (host.Allocate (shared.fIPTC_NAA_Count)); stream.SetReadPosition (shared.fIPTC_NAA_Offset); uint64 iptcOffset = stream.PositionInOriginalFile(); stream.Get (block->Buffer (), block->LogicalSize ()); SetIPTC (block, iptcOffset); } // XMP metadata. #if qDNGUseXMP if (shared.fXMPCount) { AutoPtr block (host.Allocate (shared.fXMPCount)); stream.SetReadPosition (shared.fXMPOffset); stream.Get (block->Buffer (), block->LogicalSize ()); Metadata ().SetEmbeddedXMP (host, block->Buffer (), block->LogicalSize ()); #if qDNGValidate if (!Metadata ().HaveValidEmbeddedXMP ()) { ReportError ("The embedded XMP is invalid"); } #endif } #endif // Color info. if (!IsMonochrome ()) { // If the ColorimetricReference is the ICC profile PCS, // then the data must be already be white balanced to the // ICC profile PCS white point. if (ColorimetricReference () == crICCProfilePCS) { ClearCameraNeutral (); SetCameraWhiteXY (PCStoXY ()); } else { // AsShotNeutral. if (shared.fAsShotNeutral.Count () == ColorChannels ()) { SetCameraNeutral (shared.fAsShotNeutral); } // AsShotWhiteXY. if (shared.fAsShotWhiteXY.IsValid () && !HasCameraNeutral ()) { SetCameraWhiteXY (shared.fAsShotWhiteXY); } } } } } /*****************************************************************************/ bool dng_negative::SetFourColorBayer () { if (ColorChannels () != 3) { return false; } if (!fMosaicInfo.Get ()) { return false; } if (!fMosaicInfo.Get ()->SetFourColorBayer ()) { return false; } SetColorChannels (4); if (fCameraNeutral.Count () == 3) { dng_vector n (4); n [0] = fCameraNeutral [0]; n [1] = fCameraNeutral [1]; n [2] = fCameraNeutral [2]; n [3] = fCameraNeutral [1]; fCameraNeutral = n; } fCameraCalibration1.Clear (); fCameraCalibration2.Clear (); fCameraCalibrationSignature.Clear (); for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { fCameraProfile [index]->SetFourColorBayer (); } return true; } /*****************************************************************************/ const dng_image & dng_negative::RawImage () const { if (fRawImage.Get ()) { return *fRawImage.Get (); } if (fStage1Image.Get ()) { return *fStage1Image.Get (); } if (fUnflattenedStage3Image.Get ()) { return *fUnflattenedStage3Image.Get (); } DNG_ASSERT (fStage3Image.Get (), "dng_negative::RawImage with no raw image"); return *fStage3Image.Get (); } /*****************************************************************************/ const dng_jpeg_image * dng_negative::RawJPEGImage () const { return fRawJPEGImage.Get (); } /*****************************************************************************/ void dng_negative::SetRawJPEGImage (AutoPtr &jpegImage) { fRawJPEGImage.Reset (jpegImage.Release ()); } /*****************************************************************************/ void dng_negative::ClearRawJPEGImage () { fRawJPEGImage.Reset (); } /*****************************************************************************/ void dng_negative::FindRawJPEGImageDigest (dng_host &host) const { if (fRawJPEGImageDigest.IsNull ()) { if (fRawJPEGImage.Get ()) { #if qDNGValidate dng_timer timer ("FindRawJPEGImageDigest time"); #endif fRawJPEGImageDigest = fRawJPEGImage->FindDigest (host); } else { ThrowProgramError ("No raw JPEG image"); } } } /*****************************************************************************/ void dng_negative::ReadStage1Image (dng_host &host, dng_stream &stream, dng_info &info) { // Allocate image we are reading. dng_ifd &rawIFD = *info.fIFD [info.fMainIndex].Get (); fStage1Image.Reset (host.Make_dng_image (rawIFD.Bounds (), rawIFD.fSamplesPerPixel, rawIFD.PixelType ())); // See if we should grab the compressed JPEG data. AutoPtr jpegImage; if (host.SaveDNGVersion () >= dngVersion_1_4_0_0 && !host.PreferredSize () && !host.ForPreview () && rawIFD.fCompression == ccLossyJPEG) { jpegImage.Reset (new dng_jpeg_image); } // See if we need to compute the digest of the compressed JPEG data // while reading. bool needJPEGDigest = (RawImageDigest ().IsValid () || NewRawImageDigest ().IsValid ()) && rawIFD.fCompression == ccLossyJPEG && jpegImage.Get () == NULL; dng_fingerprint jpegDigest; // Read the image. rawIFD.ReadImage (host, stream, *fStage1Image.Get (), jpegImage.Get (), needJPEGDigest ? &jpegDigest : NULL); // Remember the raw floating point bit depth, if reading from // a floating point image. if (fStage1Image->PixelType () == ttFloat) { SetRawFloatBitDepth (rawIFD.fBitsPerSample [0]); } // Remember the compressed JPEG data if we read it. if (jpegImage.Get ()) { SetRawJPEGImage (jpegImage); } // Remember the compressed JPEG digest if we computed it. if (jpegDigest.IsValid ()) { SetRawJPEGImageDigest (jpegDigest); } // We are are reading the main image, we should read the opcode lists // also. if (rawIFD.fOpcodeList1Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList1: "); } #endif fOpcodeList1.Parse (host, stream, rawIFD.fOpcodeList1Count, rawIFD.fOpcodeList1Offset); } if (rawIFD.fOpcodeList2Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList2: "); } #endif fOpcodeList2.Parse (host, stream, rawIFD.fOpcodeList2Count, rawIFD.fOpcodeList2Offset); } if (rawIFD.fOpcodeList3Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList3: "); } #endif fOpcodeList3.Parse (host, stream, rawIFD.fOpcodeList3Count, rawIFD.fOpcodeList3Offset); } } /*****************************************************************************/ void dng_negative::SetStage1Image (AutoPtr &image) { fStage1Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::SetStage2Image (AutoPtr &image) { fStage2Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::SetStage3Image (AutoPtr &image) { fStage3Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::DoBuildStage2 (dng_host &host) { dng_image &stage1 = *fStage1Image.Get (); dng_linearization_info &info = *fLinearizationInfo.Get (); uint32 pixelType = ttShort; if (stage1.PixelType () == ttLong || stage1.PixelType () == ttFloat) { pixelType = ttFloat; } fStage2Image.Reset (host.Make_dng_image (info.fActiveArea.Size (), stage1.Planes (), pixelType)); info.Linearize (host, stage1, *fStage2Image.Get ()); } /*****************************************************************************/ void dng_negative::DoPostOpcodeList2 (dng_host & /* host */) { // Nothing by default. } /*****************************************************************************/ bool dng_negative::NeedDefloatStage2 (dng_host &host) { if (fStage2Image->PixelType () == ttFloat) { if (fRawImageStage >= rawImageStagePostOpcode2 && host.SaveDNGVersion () != dngVersion_None && host.SaveDNGVersion () < dngVersion_1_4_0_0) { return true; } } return false; } /*****************************************************************************/ void dng_negative::DefloatStage2 (dng_host & /* host */) { ThrowNotYetImplemented ("dng_negative::DefloatStage2"); } /*****************************************************************************/ void dng_negative::BuildStage2Image (dng_host &host) { // If reading the negative to save in DNG format, figure out // when to grab a copy of the raw data. if (host.SaveDNGVersion () != dngVersion_None) { // Transparency masks are only supported in DNG version 1.4 and // later. In this case, the flattening of the transparency mask happens // on the the stage3 image. if (TransparencyMask () && host.SaveDNGVersion () < dngVersion_1_4_0_0) { fRawImageStage = rawImageStagePostOpcode3; } else if (fOpcodeList3.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList3.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode3; } else if (host.SaveLinearDNG (*this)) { // If the opcode list 3 has optional tags that are beyond the // the minimum version, and we are saving a linear DNG anyway, // then go ahead and apply them. if (fOpcodeList3.MinVersion (true) > host.SaveDNGVersion ()) { fRawImageStage = rawImageStagePostOpcode3; } else { fRawImageStage = rawImageStagePreOpcode3; } } else if (fOpcodeList2.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList2.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode2; } else if (fOpcodeList1.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList1.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode1; } else { fRawImageStage = rawImageStagePreOpcode1; } // We should not save floating point stage1 images unless the target // DNG version is high enough to understand floating point images. // We handle this by converting from floating point to integer if // required after building stage2 image. if (fStage1Image->PixelType () == ttFloat) { if (fRawImageStage < rawImageStagePostOpcode2) { if (host.SaveDNGVersion () < dngVersion_1_4_0_0) { fRawImageStage = rawImageStagePostOpcode2; } } } } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePreOpcode1) { fRawImage.Reset (fStage1Image->Clone ()); if (fTransparencyMask.Get ()) { fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); } } else { // If we are not keeping the most raw image, we need // to recompute the raw image digest. ClearRawImageDigest (); // If we don't grab the unprocessed stage 1 image, then // the raw JPEG image is no longer valid. ClearRawJPEGImage (); // Nor is the digest of the raw JPEG data. ClearRawJPEGImageDigest (); // We also don't know the raw floating point bit depth. SetRawFloatBitDepth (0); } // Process opcode list 1. host.ApplyOpcodeList (fOpcodeList1, *this, fStage1Image); // See if we are done with the opcode list 1. if (fRawImageStage > rawImageStagePreOpcode1) { fOpcodeList1.Clear (); } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePostOpcode1) { fRawImage.Reset (fStage1Image->Clone ()); if (fTransparencyMask.Get ()) { fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); } } // Finalize linearization info. { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.PostParse (host, *this); } // Perform the linearization. DoBuildStage2 (host); // Delete the stage1 image now that we have computed the stage 2 image. fStage1Image.Reset (); // Are we done with the linearization info. if (fRawImageStage > rawImageStagePostOpcode1) { ClearLinearizationInfo (); } // Process opcode list 2. host.ApplyOpcodeList (fOpcodeList2, *this, fStage2Image); // See if we are done with the opcode list 2. if (fRawImageStage > rawImageStagePostOpcode1) { fOpcodeList2.Clear (); } // Hook for any required processing just after opcode list 2. DoPostOpcodeList2 (host); // Convert from floating point to integer if required. if (NeedDefloatStage2 (host)) { DefloatStage2 (host); } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePostOpcode2) { fRawImage.Reset (fStage2Image->Clone ()); if (fTransparencyMask.Get ()) { fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); } } } /*****************************************************************************/ void dng_negative::DoInterpolateStage3 (dng_host &host, int32 srcPlane) { dng_image &stage2 = *fStage2Image.Get (); dng_mosaic_info &info = *fMosaicInfo.Get (); dng_point downScale = info.DownScale (host.MinimumSize (), host.PreferredSize (), host.CropFactor ()); if (downScale != dng_point (1, 1)) { SetIsPreview (true); } dng_point dstSize = info.DstSize (downScale); fStage3Image.Reset (host.Make_dng_image (dng_rect (dstSize), info.fColorPlanes, stage2.PixelType ())); if (srcPlane < 0 || srcPlane >= (int32) stage2.Planes ()) { srcPlane = 0; } info.Interpolate (host, *this, stage2, *fStage3Image.Get (), downScale, srcPlane); } /*****************************************************************************/ // Interpolate and merge a multi-channel CFA image. void dng_negative::DoMergeStage3 (dng_host &host) { // The DNG SDK does not provide multi-channel CFA image merging code. // It just grabs the first channel and uses that. DoInterpolateStage3 (host, 0); // Just grabbing the first channel would often result in the very // bright image using the baseline exposure value. fStage3Gain = pow (2.0, BaselineExposure ()); } /*****************************************************************************/ void dng_negative::DoBuildStage3 (dng_host &host, int32 srcPlane) { // If we don't have a mosaic pattern, then just move the stage 2 // image on to stage 3. dng_mosaic_info *info = fMosaicInfo.Get (); if (!info || !info->IsColorFilterArray ()) { fStage3Image.Reset (fStage2Image.Release ()); } else { // Remember the size of the stage 2 image. dng_point stage2_size = fStage2Image->Size (); // Special case multi-channel CFA interpolation. if ((fStage2Image->Planes () > 1) && (srcPlane < 0)) { DoMergeStage3 (host); } // Else do a single channel interpolation. else { DoInterpolateStage3 (host, srcPlane); } // Calculate the ratio of the stage 3 image size to stage 2 image size. dng_point stage3_size = fStage3Image->Size (); fRawToFullScaleH = (real64) stage3_size.h / (real64) stage2_size.h; fRawToFullScaleV = (real64) stage3_size.v / (real64) stage2_size.v; } } /*****************************************************************************/ void dng_negative::BuildStage3Image (dng_host &host, int32 srcPlane) { // Finalize the mosaic information. dng_mosaic_info *info = fMosaicInfo.Get (); if (info) { info->PostParse (host, *this); } // Do the interpolation as required. DoBuildStage3 (host, srcPlane); // Delete the stage2 image now that we have computed the stage 3 image. fStage2Image.Reset (); // Are we done with the mosaic info? if (fRawImageStage >= rawImageStagePreOpcode3) { ClearMosaicInfo (); // To support saving linear DNG files, to need to account for // and upscaling during interpolation. if (fRawToFullScaleH > 1.0) { uint32 adjust = Round_uint32 (fRawToFullScaleH); fDefaultCropSizeH .n = SafeUint32Mult (fDefaultCropSizeH.n, adjust); fDefaultCropOriginH.n = SafeUint32Mult (fDefaultCropOriginH.n, adjust); fDefaultScaleH .d = SafeUint32Mult (fDefaultScaleH.d, adjust); fRawToFullScaleH /= (real64) adjust; } if (fRawToFullScaleV > 1.0) { uint32 adjust = Round_uint32 (fRawToFullScaleV); fDefaultCropSizeV .n = SafeUint32Mult (fDefaultCropSizeV.n, adjust); fDefaultCropOriginV.n = SafeUint32Mult (fDefaultCropOriginV.n, adjust); fDefaultScaleV .d = SafeUint32Mult (fDefaultScaleV.d, adjust); fRawToFullScaleV /= (real64) adjust; } } // Resample the transparency mask if required. ResizeTransparencyToMatchStage3 (host); // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePreOpcode3) { fRawImage.Reset (fStage3Image->Clone ()); if (fTransparencyMask.Get ()) { fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); } } // Process opcode list 3. host.ApplyOpcodeList (fOpcodeList3, *this, fStage3Image); // See if we are done with the opcode list 3. if (fRawImageStage > rawImageStagePreOpcode3) { fOpcodeList3.Clear (); } // Just in case the opcode list 3 changed the image size, resample the // transparency mask again if required. This is nearly always going // to be a fast NOP operation. ResizeTransparencyToMatchStage3 (host); // Don't need to grab a copy of raw data at this stage since // it is kept around as the stage 3 image. } /******************************************************************************/ class dng_gamma_encode_proxy : public dng_1d_function { private: real64 fBlack; real64 fWhite; bool fIsSceneReferred; real64 scale; real64 t1; public: dng_gamma_encode_proxy (real64 black, real64 white, bool isSceneReferred) : fBlack (black) , fWhite (white) , fIsSceneReferred (isSceneReferred) , scale (1.0 / (fWhite - fBlack)) , t1 (1.0 / (27.0 * pow (5.0, 3.0 / 2.0))) { } virtual real64 Evaluate (real64 x) const { x = Pin_real64 (0.0, (x - fBlack) * scale, 1.0); real64 y; if (fIsSceneReferred) { real64 t = pow (sqrt (25920.0 * x * x + 1.0) * t1 + x * (8.0 / 15.0), 1.0 / 3.0); y = t - 1.0 / (45.0 * t); DNG_ASSERT (Abs_real64 (x - (y / 16.0 + y * y * y * 15.0 / 16.0)) < 0.0000001, "Round trip error"); } else { y = (sqrt (960.0 * x + 1.0) - 1.0) / 30.0; DNG_ASSERT (Abs_real64 (x - (y / 16.0 + y * y * (15.0 / 16.0))) < 0.0000001, "Round trip error"); } return y; } }; /*****************************************************************************/ class dng_encode_proxy_task: public dng_area_task { private: const dng_image &fSrcImage; dng_image &fDstImage; AutoPtr fTable16 [kMaxColorPlanes]; public: dng_encode_proxy_task (dng_host &host, const dng_image &srcImage, dng_image &dstImage, const real64 *black, const real64 *white, bool isSceneReferred); virtual dng_rect RepeatingTile1 () const { return fSrcImage.RepeatingTile (); } virtual dng_rect RepeatingTile2 () const { return fDstImage.RepeatingTile (); } virtual void Process (uint32 threadIndex, const dng_rect &tile, dng_abort_sniffer *sniffer); private: // Hidden copy constructor and assignment operator. dng_encode_proxy_task (const dng_encode_proxy_task &task); dng_encode_proxy_task & operator= (const dng_encode_proxy_task &task); }; /*****************************************************************************/ dng_encode_proxy_task::dng_encode_proxy_task (dng_host &host, const dng_image &srcImage, dng_image &dstImage, const real64 *black, const real64 *white, bool isSceneReferred) : fSrcImage (srcImage) , fDstImage (dstImage) { for (uint32 plane = 0; plane < fSrcImage.Planes (); plane++) { dng_gamma_encode_proxy gamma (black [plane], white [plane], isSceneReferred); dng_1d_table table32; table32.Initialize (host.Allocator (), gamma); fTable16 [plane] . Reset (host.Allocate (0x10000 * sizeof (uint16))); table32.Expand16 (fTable16 [plane]->Buffer_uint16 ()); } } /*****************************************************************************/ void dng_encode_proxy_task::Process (uint32 /* threadIndex */, const dng_rect &tile, dng_abort_sniffer * /* sniffer */) { dng_const_tile_buffer srcBuffer (fSrcImage, tile); dng_dirty_tile_buffer dstBuffer (fDstImage, tile); int32 sColStep = srcBuffer.fColStep; int32 dColStep = dstBuffer.fColStep; const uint16 *noise = dng_dither::Get ().NoiseBuffer16 (); for (uint32 plane = 0; plane < fSrcImage.Planes (); plane++) { const uint16 *map = fTable16 [plane]->Buffer_uint16 (); for (int32 row = tile.t; row < tile.b; row++) { const uint16 *sPtr = srcBuffer.ConstPixel_uint16 (row, tile.l, plane); uint8 *dPtr = dstBuffer.DirtyPixel_uint8 (row, tile.l, plane); const uint16 *rPtr = &noise [(row & dng_dither::kRNGMask) * dng_dither::kRNGSize]; for (int32 col = tile.l; col < tile.r; col++) { uint32 x = *sPtr; uint32 r = rPtr [col & dng_dither::kRNGMask]; x = map [x]; x = (((x << 8) - x) + r) >> 16; *dPtr = (uint8) x; sPtr += sColStep; dPtr += dColStep; } } } } /******************************************************************************/ dng_image * dng_negative::EncodeRawProxy (dng_host &host, const dng_image &srcImage, dng_opcode_list &opcodeList) const { if (srcImage.PixelType () != ttShort) { return NULL; } real64 black [kMaxColorPlanes]; real64 white [kMaxColorPlanes]; bool isSceneReferred = (ColorimetricReference () == crSceneReferred); { const real64 kClipFraction = 0.00001; uint64 pixels = (uint64) srcImage.Bounds ().H () * (uint64) srcImage.Bounds ().W (); uint32 limit = Round_int32 ((real64) pixels * kClipFraction); AutoPtr histData (host.Allocate (65536 * sizeof (uint32))); uint32 *hist = histData->Buffer_uint32 (); for (uint32 plane = 0; plane < srcImage.Planes (); plane++) { HistogramArea (host, srcImage, srcImage.Bounds (), hist, 65535, plane); uint32 total = 0; uint32 upper = 65535; while (total + hist [upper] <= limit && upper > 255) { total += hist [upper]; upper--; } total = 0; uint32 lower = 0; while (total + hist [lower] <= limit && lower < upper - 255) { total += hist [lower]; lower++; } black [plane] = lower / 65535.0; white [plane] = upper / 65535.0; } } // Apply the gamma encoding, using dither when downsampling to 8-bit. AutoPtr dstImage (host.Make_dng_image (srcImage.Bounds (), srcImage.Planes (), ttByte)); { dng_encode_proxy_task task (host, srcImage, *dstImage, black, white, isSceneReferred); host.PerformAreaTask (task, srcImage.Bounds ()); } // Add opcodes to undo the gamma encoding. { for (uint32 plane = 0; plane < srcImage.Planes (); plane++) { dng_area_spec areaSpec (srcImage.Bounds (), plane); real64 coefficient [4]; coefficient [0] = 0.0; coefficient [1] = 1.0 / 16.0; if (isSceneReferred) { coefficient [2] = 0.0; coefficient [3] = 15.0 / 16.0; } else { coefficient [2] = 15.0 / 16.0; coefficient [3] = 0.0; } coefficient [0] *= white [plane] - black [plane]; coefficient [1] *= white [plane] - black [plane]; coefficient [2] *= white [plane] - black [plane]; coefficient [3] *= white [plane] - black [plane]; coefficient [0] += black [plane]; AutoPtr opcode (new dng_opcode_MapPolynomial (areaSpec, isSceneReferred ? 3 : 2, coefficient)); opcodeList.Append (opcode); } } return dstImage.Release (); } /******************************************************************************/ void dng_negative::AdjustProfileForStage3 () { // For dng_sdk, the stage3 image's color space is always the same as the // raw image's color space. } /******************************************************************************/ void dng_negative::ConvertToProxy (dng_host &host, dng_image_writer &writer, uint32 proxySize, uint64 proxyCount) { if (!proxySize) { proxySize = kMaxImageSide; } if (!proxyCount) { proxyCount = (uint64) proxySize * proxySize; } // Don't need to private data around in non-full size proxies. if (proxySize < kMaxImageSide || proxyCount < kMaxImageSide * kMaxImageSide) { ClearMakerNote (); ClearPrivateData (); } // See if we already have an acceptable proxy image. if (fRawImage.Get () && fRawImage->PixelType () == ttByte && fRawImage->Bounds () == DefaultCropArea () && fRawImage->Bounds ().H () <= proxySize && fRawImage->Bounds ().W () <= proxySize && (uint64) fRawImage->Bounds ().H () * (uint64) fRawImage->Bounds ().W () <= proxyCount && (!GetMosaicInfo () || !GetMosaicInfo ()->IsColorFilterArray ()) && fRawJPEGImage.Get () && (!RawTransparencyMask () || RawTransparencyMask ()->PixelType () == ttByte)) { return; } if (fRawImage.Get () && fRawImage->PixelType () == ttFloat && fRawImage->Bounds ().H () <= proxySize && fRawImage->Bounds ().W () <= proxySize && (uint64) fRawImage->Bounds ().H () * (uint64) fRawImage->Bounds ().W () <= proxyCount && RawFloatBitDepth () == 16 && (!RawTransparencyMask () || RawTransparencyMask ()->PixelType () == ttByte)) { return; } // Clear any grabbed raw image, since we are going to start // building the proxy with the stage3 image. fRawImage.Reset (); ClearRawJPEGImage (); SetRawFloatBitDepth (0); ClearLinearizationInfo (); ClearMosaicInfo (); fOpcodeList1.Clear (); fOpcodeList2.Clear (); fOpcodeList3.Clear (); // Adjust the profile to match the stage 3 image, if required. AdjustProfileForStage3 (); // Not saving the raw-most image, do the old raw digest is no // longer valid. ClearRawImageDigest (); ClearRawJPEGImageDigest (); // Trim off extra pixels outside the default crop area. dng_rect defaultCropArea = DefaultCropArea (); if (Stage3Image ()->Bounds () != defaultCropArea) { fStage3Image->Trim (defaultCropArea); if (fTransparencyMask.Get ()) { fTransparencyMask->Trim (defaultCropArea); } fDefaultCropOriginH = dng_urational (0, 1); fDefaultCropOriginV = dng_urational (0, 1); } // Figure out the requested proxy pixel size. real64 aspectRatio = AspectRatio (); dng_point newSize (proxySize, proxySize); if (aspectRatio >= 1.0) { newSize.v = Max_int32 (1, Round_int32 (proxySize / aspectRatio)); } else { newSize.h = Max_int32 (1, Round_int32 (proxySize * aspectRatio)); } newSize.v = Min_int32 (newSize.v, DefaultFinalHeight ()); newSize.h = Min_int32 (newSize.h, DefaultFinalWidth ()); if ((uint64) newSize.v * (uint64) newSize.h > proxyCount) { if (aspectRatio >= 1.0) { newSize.h = (uint32) sqrt (proxyCount * aspectRatio); newSize.v = Max_int32 (1, Round_int32 (newSize.h / aspectRatio)); } else { newSize.v = (uint32) sqrt (proxyCount / aspectRatio); newSize.h = Max_int32 (1, Round_int32 (newSize.v * aspectRatio)); } } // If this is fewer pixels, downsample the stage 3 image to that size. dng_point oldSize = defaultCropArea.Size (); if ((uint64) newSize.v * (uint64) newSize.h < (uint64) oldSize.v * (uint64) oldSize.h) { const dng_image &srcImage (*Stage3Image ()); AutoPtr dstImage (host.Make_dng_image (newSize, srcImage.Planes (), srcImage.PixelType ())); host.ResampleImage (srcImage, *dstImage); fStage3Image.Reset (dstImage.Release ()); fDefaultCropSizeH = dng_urational (newSize.h, 1); fDefaultCropSizeV = dng_urational (newSize.v, 1); fDefaultScaleH = dng_urational (1, 1); fDefaultScaleV = dng_urational (1, 1); fBestQualityScale = dng_urational (1, 1); fRawToFullScaleH = 1.0; fRawToFullScaleV = 1.0; } // Convert 32-bit floating point images to 16-bit floating point to // save space. if (Stage3Image ()->PixelType () == ttFloat) { fRawImage.Reset (host.Make_dng_image (Stage3Image ()->Bounds (), Stage3Image ()->Planes (), ttFloat)); LimitFloatBitDepth (host, *Stage3Image (), *fRawImage, 16, 32768.0f); SetRawFloatBitDepth (16); SetWhiteLevel (32768); } else { // Convert 16-bit deep images to 8-bit deep image for saving. fRawImage.Reset (EncodeRawProxy (host, *Stage3Image (), fOpcodeList2)); if (fRawImage.Get ()) { SetWhiteLevel (255); // Compute JPEG compressed version. if (fRawImage->PixelType () == ttByte && host.SaveDNGVersion () >= dngVersion_1_4_0_0) { AutoPtr jpegImage (new dng_jpeg_image); jpegImage->Encode (host, *this, writer, *fRawImage); SetRawJPEGImage (jpegImage); } } } // Deal with transparency mask. if (TransparencyMask ()) { const bool convertTo8Bit = true; ResizeTransparencyToMatchStage3 (host, convertTo8Bit); fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); } // Recompute the raw data unique ID, since we changed the image data. RecomputeRawDataUniqueID (host); } /*****************************************************************************/ dng_linearization_info * dng_negative::MakeLinearizationInfo () { dng_linearization_info *info = new dng_linearization_info (); if (!info) { ThrowMemoryFull (); } return info; } /*****************************************************************************/ void dng_negative::NeedLinearizationInfo () { if (!fLinearizationInfo.Get ()) { fLinearizationInfo.Reset (MakeLinearizationInfo ()); } } /*****************************************************************************/ dng_mosaic_info * dng_negative::MakeMosaicInfo () { dng_mosaic_info *info = new dng_mosaic_info (); if (!info) { ThrowMemoryFull (); } return info; } /*****************************************************************************/ void dng_negative::NeedMosaicInfo () { if (!fMosaicInfo.Get ()) { fMosaicInfo.Reset (MakeMosaicInfo ()); } } /*****************************************************************************/ void dng_negative::SetTransparencyMask (AutoPtr &image, uint32 bitDepth) { fTransparencyMask.Reset (image.Release ()); fRawTransparencyMaskBitDepth = bitDepth; } /*****************************************************************************/ const dng_image * dng_negative::TransparencyMask () const { return fTransparencyMask.Get (); } /*****************************************************************************/ const dng_image * dng_negative::RawTransparencyMask () const { if (fRawTransparencyMask.Get ()) { return fRawTransparencyMask.Get (); } return TransparencyMask (); } /*****************************************************************************/ uint32 dng_negative::RawTransparencyMaskBitDepth () const { if (fRawTransparencyMaskBitDepth) { return fRawTransparencyMaskBitDepth; } const dng_image *mask = RawTransparencyMask (); if (mask) { switch (mask->PixelType ()) { case ttByte: return 8; case ttShort: return 16; case ttFloat: return 32; default: ThrowProgramError (); } } return 0; } /*****************************************************************************/ void dng_negative::ReadTransparencyMask (dng_host &host, dng_stream &stream, dng_info &info) { if (info.fMaskIndex != -1) { // Allocate image we are reading. dng_ifd &maskIFD = *info.fIFD [info.fMaskIndex].Get (); fTransparencyMask.Reset (host.Make_dng_image (maskIFD.Bounds (), 1, maskIFD.PixelType ())); // Read the image. maskIFD.ReadImage (host, stream, *fTransparencyMask.Get ()); // Remember the pixel depth. fRawTransparencyMaskBitDepth = maskIFD.fBitsPerSample [0]; } } /*****************************************************************************/ void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, bool convertTo8Bit) { if (TransparencyMask ()) { if ((TransparencyMask ()->Bounds () != fStage3Image->Bounds ()) || (TransparencyMask ()->PixelType () != ttByte && convertTo8Bit)) { AutoPtr newMask (host.Make_dng_image (fStage3Image->Bounds (), 1, convertTo8Bit ? ttByte : TransparencyMask ()->PixelType ())); host.ResampleImage (*TransparencyMask (), *newMask); fTransparencyMask.Reset (newMask.Release ()); if (!fRawTransparencyMask.Get ()) { fRawTransparencyMaskBitDepth = 0; } } } } /*****************************************************************************/ bool dng_negative::NeedFlattenTransparency (dng_host & /* host */) { if (TransparencyMask ()) { return true; } return false; } /*****************************************************************************/ void dng_negative::FlattenTransparency (dng_host & /* host */) { ThrowNotYetImplemented (); } /*****************************************************************************/ const dng_image * dng_negative::UnflattenedStage3Image () const { if (fUnflattenedStage3Image.Get ()) { return fUnflattenedStage3Image.Get (); } return fStage3Image.Get (); } /*****************************************************************************/