| /*****************************************************************************/ |
| // 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<dng_noise_function> &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<dng_memory_block> &block, uint64 offset) |
| { |
| |
| fIPTCBlock.Reset (block.Release ()); |
| |
| fIPTCOffset = offset; |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_metadata::SetIPTC (AutoPtr<dng_memory_block> &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<dng_xmp> 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<dng_negative> 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<dng_camera_profile> &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<dng_camera_profile *>::iterator iter = fCameraProfile.begin (); |
| dng_std_vector<dng_camera_profile *>::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<dng_memory_block> 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<dng_fingerprint> fTileHash; |
| |
| AutoPtr<dng_memory_block> 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<dng_memory_block> &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<dng_camera_profile> 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<dng_camera_profile> 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<dng_memory_block> 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<dng_memory_block> 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<dng_memory_block> 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<dng_memory_block> 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<dng_memory_block> 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<dng_jpeg_image> &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<dng_jpeg_image> 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<dng_image> &image) |
| { |
| |
| fStage1Image.Reset (image.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetStage2Image (AutoPtr<dng_image> &image) |
| { |
| |
| fStage2Image.Reset (image.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetStage3Image (AutoPtr<dng_image> &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<dng_memory_block> 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<dng_memory_block> 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<dng_image> 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<dng_opcode> 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<dng_image> 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<dng_jpeg_image> 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<dng_image> &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<dng_image> 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 (); |
| |
| } |
| |
| /*****************************************************************************/ |