| /*****************************************************************************/ |
| // Copyright 2006-2021 Adobe Systems Incorporated |
| // All Rights Reserved. |
| // |
| // NOTICE: Adobe permits you to use, modify, and distribute this file in |
| // accordance with the terms of the Adobe license agreement accompanying it. |
| /*****************************************************************************/ |
| |
| #include "dng_negative.h" |
| |
| #include "dng_1d_table.h" |
| #include "dng_abort_sniffer.h" |
| #include "dng_area_task.h" |
| #include "dng_assertions.h" |
| #include "dng_big_table.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_file_stream.h" |
| #include "dng_gain_map.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_jxl.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_read_image.h" |
| #include "dng_resample.h" |
| #include "dng_safe_arithmetic.h" |
| #include "dng_sdk_limits.h" |
| #include "dng_tag_codes.h" |
| #include "dng_tag_values.h" |
| #include "dng_tile_iterator.h" |
| #include "dng_uncopyable.h" |
| #include "dng_utils.h" |
| |
| #if qDNGUseXMP |
| #include "dng_xmp.h" |
| #endif |
| |
| /*****************************************************************************/ |
| |
| void dng_semantic_mask::CalcMaskSubArea (dng_point &origin, |
| dng_rect &wholeImageArea) const |
| { |
| |
| origin.v = (int32) fMaskSubArea [0]; // top |
| origin.h = (int32) fMaskSubArea [1]; // left |
| |
| wholeImageArea.t = (int32) 0; |
| wholeImageArea.l = (int32) 0; |
| wholeImageArea.b = (int32) fMaskSubArea [3]; // height |
| wholeImageArea.r = (int32) fMaskSubArea [2]; // width |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_semantic_mask::IsMaskSubAreaValid () const |
| { |
| |
| // Can't do full check without mask itself. |
| |
| if (!fMask) |
| { |
| return false; |
| } |
| |
| dng_point origin; |
| |
| dng_rect wholeImageArea; |
| |
| CalcMaskSubArea (origin, wholeImageArea); |
| |
| const dng_point maskSize = fMask->Bounds ().Size (); |
| |
| dng_rect crop; |
| |
| crop.t = origin.v; |
| crop.l = origin.h; |
| crop.b = origin.v + maskSize.v; |
| crop.r = origin.h + maskSize.h; |
| |
| if ((crop & wholeImageArea) != crop) |
| { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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 (); |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_noise_profile::operator== (const dng_noise_profile &profile) const |
| { |
| |
| if (IsValid ()) |
| { |
| |
| if (!profile.IsValid ()) |
| { |
| return false; |
| } |
| |
| if (NumFunctions () != profile.NumFunctions ()) |
| { |
| return false; |
| } |
| |
| for (uint32 plane = 0; plane < NumFunctions (); plane++) |
| { |
| |
| if (NoiseFunction (plane).Scale () != profile.NoiseFunction (plane).Scale () || |
| NoiseFunction (plane).Offset () != profile.NoiseFunction (plane).Offset ()) |
| { |
| return false; |
| } |
| |
| } |
| |
| return true; |
| |
| } |
| |
| else |
| return !profile.IsValid (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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) |
| , fSourceMIME () |
| , fBigTableDictionary () |
| , fBigTableIndex () |
| |
| { |
| } |
| |
| /*****************************************************************************/ |
| |
| 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) |
| , fSourceMIME (rhs.fSourceMIME) |
| , fBigTableDictionary (rhs.fBigTableDictionary) |
| , fBigTableIndex (rhs.fBigTableIndex) |
| , fBigTableGroupIndex (rhs.fBigTableGroupIndex) |
| , fImageSequenceInfo (rhs.fImageSequenceInfo) |
| , fImageStats (rhs.fImageStats) |
| |
| { |
| |
| } |
| |
| /******************************************************************************/ |
| |
| 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 uint32 numLeadingZeroBytes) 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->CompressedData ().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->CompressedData ().Buffer (), |
| thumbnail->CompressedData ().LogicalSize ()); |
| |
| } |
| |
| // Trim the file to this length. |
| |
| stream.Flush (); |
| |
| stream.SetLength (stream.Position ()); |
| |
| } |
| |
| } |
| |
| return stream.AsMemoryBlock (allocator, |
| numLeadingZeroBytes); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| 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_direct_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.ProcessPtr (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 ()); |
| |
| dng_big_table_dictionary dictionary = BigTableDictionary (); |
| |
| DualParseXMP (host, |
| *tempXMP, |
| dictionary, |
| buffer, |
| count); |
| |
| ResetXMPSidecarNewer (tempXMP.Release (), xmpInSidecar, xmpIsNewer); |
| |
| SetBigTableDictionary (dictionary); |
| |
| 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_direct_printer printer; |
| |
| printer.ProcessPtr (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 // qDNGUseXMP |
| |
| /*****************************************************************************/ |
| |
| void dng_metadata::SynchronizeMetadata () |
| { |
| |
| DNG_REQUIRE (fExif.Get (), |
| "Expected valid fExif field in " |
| "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) |
| , fRawDefaultCropSizeH () |
| , fRawDefaultCropSizeV () |
| , fRawDefaultCropOriginH () |
| , fRawDefaultCropOriginV () |
| , fDefaultUserCropT (0, 1) |
| , fDefaultUserCropL (0, 1) |
| , fDefaultUserCropB (1, 1) |
| , fDefaultUserCropR (1, 1) |
| , fDefaultScaleH (1, 1) |
| , fDefaultScaleV (1, 1) |
| , fRawDefaultScaleH () |
| , fRawDefaultScaleV () |
| , fBestQualityScale (1, 1) |
| , fRawBestQualityScale () |
| , fOriginalDefaultFinalSize () |
| , fOriginalBestQualityFinalSize () |
| , fOriginalDefaultCropSizeH () |
| , fOriginalDefaultCropSizeV () |
| , fRawToFullScaleH (1.0) |
| , fRawToFullScaleV (1.0) |
| , fBaselineNoise (100, 100) |
| , fNoiseReductionApplied (0, 0) |
| , fRawNoiseReductionApplied (0, 0) |
| , fNoiseProfile () |
| , fRawNoiseProfile () |
| , fBaselineExposure ( 0, 100) |
| , fBaselineSharpness (100, 100) |
| , fRawBaselineSharpness (0, 0) |
| , fChromaBlurRadius () |
| , fAntiAliasStrength (100, 100) |
| , fLinearResponseLimit (100, 100) |
| , fShadowScale (1, 1) |
| , fColorimetricReference (crSceneReferred) |
| , fFloatingPoint (false) |
| , fColorChannels (0) |
| , fAnalogBalance () |
| , fCameraNeutral () |
| , fCameraWhiteXY () |
| , fCameraCalibration1 () |
| , fCameraCalibration2 () |
| , fCameraCalibration3 () |
| , 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) |
| , fStage3BlackLevel (0) |
| , fIsPreview (false) |
| , fIsDamaged (false) |
| , fRawImageStage (rawImageStageNone) |
| , fRawImage () |
| , fRawImageBlackLevel (0) |
| , fRawFloatBitDepth (0) |
| , fTransparencyMask () |
| , fRawTransparencyMask () |
| , fRawTransparencyMaskBitDepth (0) |
| , fUnflattenedStage3Image () |
| , fHasDepthMap (false) |
| , fDepthMap () |
| , fRawDepthMap () |
| , fDepthFormat (depthFormatUnknown) |
| , fDepthNear (0, 0) |
| , fDepthFar (0, 0) |
| , fDepthUnits (depthUnitsUnknown) |
| , fDepthMeasureType (depthMeasureUnknown) |
| , fEnhanceParams () |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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::SetCameraCalibration3 (const dng_matrix &m) |
| { |
| |
| fCameraCalibration3 = m; |
| |
| fCameraCalibration3.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 (); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| 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]; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::GetProfileMetadataList (dng_profile_metadata_list &list) const |
| { |
| |
| list.clear (); |
| |
| list.reserve (ProfileCount ()); |
| |
| for (uint32 index = 0; index < ProfileCount (); index++) |
| { |
| |
| list.push_back (dng_camera_profile_metadata (ProfileByIndex (index), |
| index)); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::GetProfileByMetadata |
| (const dng_camera_profile_metadata &metadata, |
| dng_camera_profile &foundProfile) const |
| { |
| |
| if (metadata.fIndex >= 0) |
| { |
| |
| foundProfile = ProfileByIndex (metadata.fIndex); |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::GetProfileByIDFromList (const dng_profile_metadata_list &list, |
| const dng_camera_profile_id &id, |
| dng_camera_profile &foundProfile, |
| bool useDefaultIfNoMatch, |
| const dng_camera_profile_group_selector *groupSelector) const |
| { |
| |
| // How many profiles in list? |
| |
| uint32 profileCount = (uint32) list.size (); |
| |
| if (profileCount == 0) |
| { |
| return false; |
| } |
| |
| // If this is a profile group ID, match group names and pick from that list. |
| |
| if (HasProfileGroupPrefix (id.Name ())) |
| { |
| |
| dng_string groupName = StripProfileGroupPrefix (id.Name ()); |
| |
| dng_camera_profile_group_selector selector; |
| |
| if (groupSelector) |
| { |
| selector = *groupSelector; |
| } |
| |
| for (uint32 pass = 1; pass <= 2; pass++) |
| { |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| if (list [index] . fGroupName == groupName) |
| { |
| |
| if (pass == 1 && (selector.fHDR != list [index] . fHDR)) |
| { |
| continue; |
| } |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // If we have both a profile name and fingerprint, try matching both. |
| |
| if (id.Name ().NotEmpty () && id.Fingerprint ().IsValid ()) |
| { |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| const dng_camera_profile_id &profileID (list [index] . fProfileID); |
| |
| if (id == profileID) |
| { |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // If we have a name, try matching that. |
| |
| if (id.Name ().NotEmpty ()) |
| { |
| |
| // Try case sensitive match first, then ignore case. |
| |
| for (uint32 pass = 1; pass <= 2; pass++) |
| { |
| |
| bool case_sensitive = (pass == 1); |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| const dng_camera_profile_id &profileID (list [index] . fProfileID); |
| |
| if (id.Name ().Matches (profileID.Name ().Get (), case_sensitive)) |
| { |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // If we have a valid fingerprint, try matching that. Since the fingerprint |
| // includes the profile name, we only do this if the name is null. |
| |
| else if (id.Fingerprint ().IsValid ()) |
| { |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| const dng_camera_profile_id &profileID (list [index] . fProfileID); |
| |
| if (id.Fingerprint () == profileID.Fingerprint ()) |
| { |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // Try "upgrading" (or downgrading if required) profile name versions. |
| |
| if (id.Name ().NotEmpty ()) |
| { |
| |
| dng_string baseName; |
| int32 version; |
| |
| SplitCameraProfileName (id.Name (), |
| baseName, |
| version); |
| |
| int32 bestIndex = -1; |
| int32 bestVersion = 0; |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| const dng_camera_profile_id &profileID (list [index] . fProfileID); |
| |
| if (profileID.Name ().StartsWith (baseName.Get ())) |
| { |
| |
| dng_string testBaseName; |
| int32 testVersion; |
| |
| SplitCameraProfileName (profileID.Name (), |
| testBaseName, |
| testVersion); |
| |
| if (testBaseName.Matches (baseName.Get ())) |
| { |
| |
| if (bestIndex == -1 || testVersion > bestVersion) |
| { |
| |
| bestIndex = index; |
| bestVersion = testVersion; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| if (bestIndex != -1) |
| { |
| |
| if (GetProfileByMetadata (list [bestIndex], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| // Did not find a match any way. See if we should return a default value. |
| |
| if (useDefaultIfNoMatch) |
| { |
| |
| if (GetProfileByMetadata (list [0], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| // Found nothing. |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::GetProfileToEmbedFromList (const dng_profile_metadata_list &list, |
| const dng_metadata & /* metadata */, |
| dng_camera_profile &foundProfile, |
| bool /* skipAdobeStandard = false */) const |
| { |
| |
| // How many profiles in list? |
| |
| uint32 profileCount = (uint32) list.size (); |
| |
| if (profileCount == 0) |
| { |
| return false; |
| } |
| |
| // First try to look for the first profile that was already in the DNG |
| // when we read it. |
| |
| { |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| if (list [index] . fWasReadFromDNG) |
| { |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // Next we look for the first profile that is legal to embed. |
| |
| { |
| |
| for (uint32 index = 0; index < profileCount; index++) |
| { |
| |
| if (list [index] . fIsLegalToEmbed) |
| { |
| |
| if (GetProfileByMetadata (list [index], |
| foundProfile)) |
| { |
| return true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // Else just return the first profile. |
| |
| return GetProfileByMetadata (list [0], |
| foundProfile); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::GetProfileByID (const dng_camera_profile_id &id, |
| dng_camera_profile &foundProfile, |
| bool useDefaultIfNoMatch, |
| const dng_camera_profile_group_selector *groupSelector) const |
| { |
| |
| // Monochrome negatives don't have profiles. |
| |
| if (IsMonochrome ()) |
| { |
| return false; |
| } |
| |
| // Get list of profile metadata. |
| |
| dng_profile_metadata_list list; |
| |
| GetProfileMetadataList (list); |
| |
| // Search the list. |
| |
| return GetProfileByIDFromList (list, |
| id, |
| foundProfile, |
| useDefaultIfNoMatch, |
| groupSelector); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::GetProfileToEmbed (const dng_metadata &metadata, |
| dng_camera_profile &foundProfile, |
| bool skipAdobeStandard /* = false */) const |
| { |
| |
| // Monochrome negatives don't have profiles. |
| |
| if (IsMonochrome ()) |
| { |
| return false; |
| } |
| |
| // Get list of profile metadata. |
| |
| dng_profile_metadata_list list; |
| |
| GetProfileMetadataList (list); |
| |
| // Search the list. |
| |
| return GetProfileToEmbedFromList (list, |
| metadata, |
| foundProfile, |
| skipAdobeStandard); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_color_spec * dng_negative::MakeColorSpec (const dng_camera_profile_id &id, |
| bool allowStubbed) const |
| { |
| |
| dng_camera_profile profile; |
| |
| bool haveProfile = GetProfileByID (id, profile); |
| |
| dng_color_spec *spec = new dng_color_spec (*this, |
| haveProfile ? &profile : NULL, |
| allowStubbed); |
| |
| if (!spec) |
| { |
| ThrowMemoryFull (); |
| } |
| |
| return spec; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_negative::FindImageDigest (dng_host &host, |
| const dng_image &image) |
| { |
| |
| dng_md5_direct_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)) |
| { |
| |
| ThrowOverflow ("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.ProcessPtr (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) |
| |
| : dng_area_task ("dng_find_new_raw_image_digest_task") |
| |
| , 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_rect & /* dstArea */, |
| 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_direct_printer printer; |
| |
| printer.ProcessPtr (buffer.fData, count); |
| |
| fTileHash [tileIndex] = printer.Result (); |
| |
| } |
| |
| dng_fingerprint Result () |
| { |
| |
| dng_md5_direct_printer printer; |
| |
| for (uint32 tileIndex = 0; tileIndex < fTileCount; tileIndex++) |
| { |
| |
| printer.Process (fTileHash [tileIndex]); |
| |
| } |
| |
| return printer.Result (); |
| |
| } |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_negative::FindFastImageDigest (dng_host &host, |
| const dng_image &image, |
| uint32 pixelType) |
| { |
| |
| dng_find_new_raw_image_digest_task task (image, pixelType); |
| |
| host.PerformAreaTask (task, image.Bounds ()); |
| |
| return task.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. |
| |
| fNewRawImageDigest = FindFastImageDigest (host, rawImage, rawPixelType); |
| |
| } |
| |
| // If there is a transparency 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_direct_printer printer; |
| |
| printer.Process (fNewRawImageDigest); |
| |
| printer.Process (maskDigest); |
| |
| 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; |
| |
| if (RawLossyCompressedImageDigest ().IsValid () || |
| RawLossyCompressedImage ()) |
| { |
| |
| FindRawLossyCompressedImageDigest (host); |
| |
| if (rawDigest != RawLossyCompressedImageDigest ()) |
| { |
| |
| #if qDNGValidate |
| |
| ReportError ("NewRawImageDigest does not match Lossy Compressed image"); |
| |
| #endif |
| |
| SetIsDamaged (true); |
| |
| } |
| |
| } |
| |
| else if (fTransparencyMaskWasLossyCompressed) |
| { |
| |
| // We currently don't have a defined way of computing digest |
| // that safely round trips in this case (lossless main image |
| // and lossy transparancy mask). |
| |
| } |
| |
| // 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 |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_negative::RawDataUniqueID () const |
| { |
| |
| dng_lock_std_mutex lock (fRawDataUniqueIDMutex); |
| |
| if (fRawDataUniqueID.IsValid () && fEnhanceParams.NotEmpty ()) |
| { |
| |
| dng_md5_direct_printer printer; |
| |
| printer.Process (fRawDataUniqueID); |
| |
| printer.ProcessPtr (fEnhanceParams.Get (), |
| fEnhanceParams.Length ()); |
| |
| return printer.Result (); |
| |
| } |
| |
| return fRawDataUniqueID; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| // 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 (RawDataUniqueID ().IsNull ()) |
| { |
| |
| dng_md5_printer_le_stream printer; |
| |
| // If we have a raw lossy 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 RawLossyCompressedImageDigest. |
| |
| if (RawLossyCompressedImage () && !RawTransparencyMask ()) |
| { |
| |
| FindRawLossyCompressedImageDigest (host); |
| |
| printer.Put (fRawLossyCompressedImageDigest); |
| |
| } |
| |
| // Include the new raw image digest in the unique ID. |
| |
| else |
| { |
| |
| FindNewRawImageDigest (host); |
| |
| printer.Put (fNewRawImageDigest); |
| |
| } |
| |
| // 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); |
| |
| dng_lock_std_mutex lock (fRawDataUniqueIDMutex); |
| |
| 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_direct_printer printer; |
| |
| printer.ProcessPtr (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 (); |
| |
| dng_camera_profile profile; |
| |
| if (GetProfileByID (profileID, 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 < kMaxColorPlanes; 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 < kMaxColorPlanes; 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::Set6x6Blacks (real64 blacks6x6 [36], |
| int32 plane) |
| { |
| |
| NeedLinearizationInfo (); |
| |
| dng_linearization_info &info = *fLinearizationInfo.Get (); |
| |
| info.fBlackLevelRepeatRows = 6; |
| info.fBlackLevelRepeatCols = 6; |
| |
| if (plane < 0) |
| { |
| |
| // Apply the black levels to each image plane up to kMaxColorPlanes. |
| |
| for (uint32 p = 0; p < kMaxColorPlanes; p++) |
| { |
| |
| uint32 m = 0; |
| |
| for (uint32 r = 0; r < info.fBlackLevelRepeatRows; r++) |
| for (uint32 c = 0; c < info.fBlackLevelRepeatCols; c++) |
| { |
| |
| info.fBlackLevel [r] [c] [p] = blacks6x6 [m]; |
| |
| m++; |
| |
| } |
| } |
| } |
| |
| else |
| { |
| |
| uint32 m = 0; |
| |
| // Apply the black levels to a single plane. |
| |
| for (uint32 r = 0; r < info.fBlackLevelRepeatRows; r++) |
| for (uint32 c = 0; c < info.fBlackLevelRepeatCols; c++) |
| { |
| |
| info.fBlackLevel [r] [c] [plane] = blacks6x6 [m]; |
| |
| m++; |
| |
| } |
| |
| } |
| |
| info.RoundBlacks (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetRowBlacks (const real64 *blacks, |
| uint32 count) |
| { |
| |
| if (count) |
| { |
| |
| NeedLinearizationInfo (); |
| |
| dng_linearization_info &info = *fLinearizationInfo.Get (); |
| |
| dng_safe_uint32 byteCount = |
| dng_safe_uint32 (count) * (uint32) sizeof (real64); |
| |
| info.fBlackDeltaV.Reset (Allocator ().Allocate (byteCount.Get ())); |
| |
| DoCopyBytes (blacks, |
| info.fBlackDeltaV->Buffer (), |
| byteCount.Get ()); |
| |
| 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 (); |
| |
| dng_safe_uint32 byteCount = |
| dng_safe_uint32 (count) * (uint32) sizeof (real64); |
| |
| info.fBlackDeltaH.Reset (Allocator ().Allocate (byteCount.Get ())); |
| |
| DoCopyBytes (blacks, |
| info.fBlackDeltaH->Buffer (), |
| byteCount.Get ()); |
| |
| 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 < kMaxColorPlanes; 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 < 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]; |
| |
| // 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. |
| |
| // Kludge: DNG spec says that NoiseReductionApplied tag should be in the |
| // Raw IFD, not main IFD. However, our original DNG SDK implementation |
| // read/wrote this tag from/to the main IFD. We now support reading the |
| // NoiseReductionApplied tag from both locations, but prefer the raw IFD |
| // (correct location). |
| |
| if (rawIFD.fNoiseReductionApplied.IsValid ()) |
| { |
| |
| SetNoiseReductionApplied (rawIFD.fNoiseReductionApplied); |
| |
| } |
| |
| else |
| { |
| |
| const dng_ifd &ifd0 = *info.fIFD [0]; |
| |
| SetNoiseReductionApplied (ifd0.fNoiseReductionApplied); |
| |
| } |
| |
| // NoiseProfile. |
| |
| // Kludge: DNG spec says that NoiseProfile tag should be in the Raw IFD, |
| // not main IFD. However, our original DNG SDK implementation read/wrote |
| // this tag from/to the main IFD. We now support reading the NoiseProfile |
| // tag from both locations, but prefer the raw IFD (correct location). |
| |
| if (rawIFD.fNoiseProfile.IsValid ()) |
| { |
| |
| SetNoiseProfile (rawIFD.fNoiseProfile); |
| |
| } |
| |
| else |
| { |
| |
| const dng_ifd &ifd0 = *info.fIFD [0]; |
| |
| SetNoiseProfile (ifd0.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); |
| |
| // Floating point flag. |
| |
| SetFloatingPoint (rawIFD.fSampleFormat [0] == sfFloatingPoint); |
| |
| // 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.fCameraCalibration3.NotEmpty ()) |
| { |
| |
| SetCameraCalibration3 (shared.fCameraCalibration3); |
| |
| } |
| |
| if (shared.fCameraCalibration1.NotEmpty () || |
| shared.fCameraCalibration2.NotEmpty () || |
| shared.fCameraCalibration3.NotEmpty ()) |
| { |
| |
| SetCameraCalibrationSignature (shared.fCameraCalibrationSignature.Get ()); |
| |
| } |
| |
| // ProfileGainTableMap and ProfileGainTableMap2. |
| |
| { |
| |
| // ProfileGainTableMap2 usage is IFD 0 and supercedes |
| // ProfileGainTablMap (whose usage is Raw IFD). |
| |
| const dng_ifd &ifd0 = *info.fIFD [0]; |
| |
| if (ifd0.fProfileGainTableMap) |
| { |
| |
| SetProfileGainTableMap (ifd0.fProfileGainTableMap); |
| |
| // Also mirror to main camera profile. |
| |
| shared.fCameraProfile.fProfileGainTableMap = |
| ifd0.fProfileGainTableMap; |
| |
| } |
| |
| else |
| SetProfileGainTableMap (rawIFD.fProfileGainTableMap); |
| |
| } |
| |
| // 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); |
| |
| } |
| |
| if (shared.fDepthFormat != depthFormatUnknown) |
| { |
| |
| SetDepthFormat (shared.fDepthFormat); |
| |
| } |
| |
| if (shared.fDepthNear != dng_urational (0, 0)) |
| { |
| |
| SetDepthNear (shared.fDepthNear); |
| |
| } |
| |
| if (shared.fDepthFar != dng_urational (0, 0)) |
| { |
| |
| SetDepthFar (shared.fDepthFar); |
| |
| } |
| |
| if (shared.fDepthUnits != depthUnitsUnknown) |
| { |
| |
| SetDepthUnits (shared.fDepthUnits); |
| |
| } |
| |
| if (shared.fDepthMeasureType != depthMeasureUnknown) |
| { |
| |
| SetDepthMeasureType (shared.fDepthMeasureType); |
| |
| } |
| |
| // Prefer some values from the enhanced IFD, if present and we |
| // are not ignoring it. |
| |
| if (info.fEnhancedIndex != -1 && !host.IgnoreEnhanced ()) |
| { |
| |
| dng_ifd &enhancedIFD = *info.fIFD [info.fEnhancedIndex]; |
| |
| // Remember the enhance parameters. |
| |
| SetEnhanceParams (enhancedIFD.fEnhanceParams); |
| |
| // Note: Call the various SetRaw... routines even if there are no |
| // corresponding tags in the enhanced IFD. This way, the tag values are |
| // correctly associated with the raw (original, non-enhanced) data and not |
| // with the enhanced IFD. This distinction matters when converting a DNG |
| // to a derived DNG. |
| |
| // Baseline sharpness. |
| |
| SetRawBaselineSharpness (); |
| |
| if (enhancedIFD.fBaselineSharpness.IsValid ()) |
| { |
| |
| SetBaselineSharpness (enhancedIFD.fBaselineSharpness.As_real64 ()); |
| |
| } |
| |
| // Noise reduction applied. |
| |
| SetRawNoiseReductionApplied (); |
| |
| if (enhancedIFD.fNoiseReductionApplied.IsValid ()) |
| { |
| |
| SetNoiseReductionApplied (enhancedIFD.fNoiseReductionApplied); |
| |
| } |
| |
| // Noise profile. |
| |
| SetRawNoiseProfile (); |
| |
| if (enhancedIFD.fNoiseProfile.IsValidForNegative (*this)) |
| { |
| |
| SetNoiseProfile (enhancedIFD.fNoiseProfile); |
| |
| } |
| |
| // Default scale. |
| |
| SetRawDefaultScale (); |
| |
| if (enhancedIFD.fDefaultScaleH.IsValid () && |
| enhancedIFD.fDefaultScaleV.IsValid ()) |
| { |
| |
| SetDefaultScale (enhancedIFD.fDefaultScaleH, |
| enhancedIFD.fDefaultScaleV); |
| |
| } |
| |
| // Best quality scale. |
| |
| SetRawBestQualityScale (); |
| |
| if (enhancedIFD.fBestQualityScale.IsValid ()) |
| { |
| |
| fBestQualityScale = enhancedIFD.fBestQualityScale; |
| |
| } |
| |
| // Default crop. |
| |
| SetRawDefaultCrop (); |
| |
| if (enhancedIFD.fDefaultCropSizeH.IsValid () && |
| enhancedIFD.fDefaultCropSizeV.IsValid ()) |
| { |
| |
| fDefaultCropSizeH = enhancedIFD.fDefaultCropSizeH; |
| fDefaultCropSizeV = enhancedIFD.fDefaultCropSizeV; |
| |
| if (enhancedIFD.fDefaultCropOriginH.IsValid ()) |
| { |
| fDefaultCropOriginH = enhancedIFD.fDefaultCropOriginH; |
| } |
| else |
| { |
| fDefaultCropOriginH = dng_urational (0, 1); |
| } |
| |
| if (enhancedIFD.fDefaultCropOriginV.IsValid ()) |
| { |
| fDefaultCropOriginV = enhancedIFD.fDefaultCropOriginV; |
| } |
| else |
| { |
| fDefaultCropOriginV = dng_urational (0, 1); |
| } |
| |
| } |
| |
| } |
| |
| // Image Stats. |
| |
| if (rawIFD.fImageStats.IsValidForPlaneCount (ColorChannels ()) && |
| (rawIFD.fImageStats.TagCount () > 0)) |
| { |
| |
| fMetadata.SetImageStats (rawIFD.fImageStats); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ClearOriginalSizes () |
| { |
| |
| fOriginalDefaultFinalSize = dng_point (); |
| |
| fOriginalBestQualityFinalSize = dng_point (); |
| |
| fOriginalDefaultCropSizeH.Clear (); |
| fOriginalDefaultCropSizeV.Clear (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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::SetOriginalSizes (const dng_point &size) |
| { |
| |
| SetOriginalDefaultFinalSize (size); |
| |
| SetOriginalBestQualityFinalSize (size); |
| |
| SetOriginalDefaultCropSize (dng_urational (size.h, 1), |
| dng_urational (size.v, 1)); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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 () && info.fTIFFBlockOriginalOffset != kDNGStreamInvalidOffset) |
| { |
| |
| AutoPtr<dng_memory_block> block (host.Allocate (shared.fMakerNoteCount)); |
| |
| stream.SetReadPosition (shared.fMakerNoteOffset + info.fTIFFBlockOriginalOffset - |
| info.fTIFFBlockOffset); |
| |
| 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 // qDNGUseXMP |
| |
| // Embedded big tables. |
| |
| if (shared.fBigTableDigests.size ()) |
| { |
| |
| dng_big_table_index bigTableIndex; |
| |
| for (uint32 j = 0; j < (uint32) shared.fBigTableDigests.size (); j++) |
| { |
| |
| if (!shared.fBigTableDigests [j].IsValid () || |
| !shared.fBigTableOffsets [j] || |
| !shared.fBigTableByteCounts [j]) |
| { |
| continue; |
| } |
| |
| bigTableIndex.AddEntry (shared.fBigTableDigests [j], |
| shared.fBigTableByteCounts [j], |
| shared.fBigTableOffsets [j]); |
| |
| } |
| |
| if (!bigTableIndex.IsEmpty ()) |
| { |
| |
| Metadata ().SetBigTableIndex (bigTableIndex); |
| |
| } |
| |
| // Big table group index. |
| |
| if (!shared.fBigTableGroupIndex.empty ()) |
| { |
| |
| dng_big_table_group_index index; |
| |
| for (const auto &group : shared.fBigTableGroupIndex) |
| { |
| |
| if (group.first .IsValid () && |
| group.second.IsValid ()) |
| { |
| |
| index.AddEntry (group.first, |
| group.second); |
| |
| } |
| |
| } |
| |
| if (!index.IsEmpty ()) |
| { |
| |
| Metadata ().SetBigTableGroupIndex (index); |
| |
| } |
| |
| } |
| |
| } |
| |
| // 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 (IsOutputReferred ()) |
| { |
| |
| ClearCameraNeutral (); |
| |
| SetCameraWhiteXY (PCStoXY ()); |
| |
| } |
| |
| else |
| { |
| |
| // AsShotNeutral. |
| |
| if (shared.fAsShotNeutral.Count () == ColorChannels ()) |
| { |
| |
| SetCameraNeutral (shared.fAsShotNeutral); |
| |
| } |
| |
| // AsShotWhiteXY. |
| |
| if (shared.fAsShotWhiteXY.IsValid () && !HasCameraNeutral ()) |
| { |
| |
| SetCameraWhiteXY (shared.fAsShotWhiteXY); |
| |
| } |
| |
| } |
| |
| } // color info |
| |
| // Image sequence info. |
| |
| if (shared.fImageSequenceInfo.IsValid ()) |
| { |
| |
| fMetadata.SetImageSequenceInfo (shared.fImageSequenceInfo); |
| |
| } |
| |
| } // needs meta |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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 (); |
| fCameraCalibration3.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_REQUIRE (fStage3Image.Get (), |
| "dng_negative::RawImage with no raw image"); |
| |
| return *fStage3Image.Get (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| uint16 dng_negative::RawImageBlackLevel () const |
| { |
| |
| if (fRawImage.Get ()) |
| { |
| return fRawImageBlackLevel; |
| } |
| |
| if (fStage1Image.Get ()) |
| { |
| return 0; |
| } |
| |
| return fStage3BlackLevel; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_lossy_compressed_image * dng_negative::RawLossyCompressedImage () const |
| { |
| |
| return fRawLossyCompressedImage.Get (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetRawLossyCompressedImage (AutoPtr<dng_lossy_compressed_image> &image) |
| { |
| |
| fRawLossyCompressedImage.Reset (image.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ClearRawLossyCompressedImage () |
| { |
| |
| fRawLossyCompressedImage.Reset (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::FindRawLossyCompressedImageDigest (dng_host &host) const |
| { |
| |
| if (fRawLossyCompressedImageDigest.IsNull ()) |
| { |
| |
| if (fRawLossyCompressedImage.Get ()) |
| { |
| |
| #if qDNGValidate |
| |
| dng_timer timer ("FindRawLossyCompressedImageDigest"); |
| |
| #endif |
| |
| fRawLossyCompressedImageDigest = fRawLossyCompressedImage->FindDigest (host); |
| |
| } |
| |
| else |
| { |
| |
| ThrowProgramError ("No raw lossy compressed image"); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ReadOpcodeLists (dng_host &host, |
| dng_stream &stream, |
| dng_info &info) |
| { |
| |
| dng_ifd &rawIFD = *info.fIFD [info.fMainIndex]; |
| |
| 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::ReadStage1Image (dng_host &host, |
| dng_stream &stream, |
| dng_info &info) |
| { |
| |
| // Allocate image we are reading. |
| |
| dng_ifd &rawIFD = *info.fIFD [info.fMainIndex]; |
| |
| fStage1Image.Reset (host.Make_dng_image (rawIFD.Bounds (), |
| rawIFD.fSamplesPerPixel, |
| rawIFD.PixelType ())); |
| |
| // See if we should grab the lossy compressed data. |
| |
| AutoPtr<dng_lossy_compressed_image> lossyImage (KeepLossyCompressedImage (host, |
| rawIFD)); |
| |
| // See if we need to compute the digest of the compressed JPEG or JPEG XL |
| // data while reading. |
| |
| bool needLossyDigest = ((RawImageDigest ().IsValid () || |
| NewRawImageDigest ().IsValid ()) && |
| |
| ((rawIFD.fCompression == ccLossyJPEG || |
| rawIFD.fCompression == ccJXL) && |
| (lossyImage.Get () == NULL))); |
| |
| dng_fingerprint lossyDigest; |
| |
| // Read the image. |
| |
| rawIFD.ReadImage (host, |
| stream, |
| *fStage1Image.Get (), |
| lossyImage.Get (), |
| needLossyDigest ? &lossyDigest : NULL); |
| |
| // Remember the raw floating point bit depth, if reading from |
| // a floating point image. |
| |
| if (fStage1Image->PixelType () == ttFloat) |
| { |
| |
| // If we are reading from a lossy JXL floating point image, and |
| // we are not keeping the lossy compressed data, we need to treat |
| // the raw floating point image as 32 bit, and ignore the bit |
| // depth that we fed into the JXL decoder. |
| |
| if (lossyImage.Get () || rawIFD.fCompression != ccJXL) |
| { |
| |
| SetRawFloatBitDepth (rawIFD.fBitsPerSample [0]); |
| |
| } |
| |
| } |
| |
| // Remember the compressed JPEG data if we read it. |
| |
| if (lossyImage.Get ()) |
| { |
| |
| SetRawLossyCompressedImage (lossyImage); |
| |
| } |
| |
| // Remember the compressed JPEG digest if we computed it. |
| |
| if (lossyDigest.IsValid ()) |
| { |
| |
| SetRawLossyCompressedImageDigest (lossyDigest); |
| |
| } |
| |
| // We are are reading the main image, we should read the opcode lists |
| // also. |
| |
| ReadOpcodeLists (host, |
| stream, |
| info); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ReadEnhancedImage (dng_host &host, |
| dng_stream &stream, |
| dng_info &info) |
| { |
| |
| // Should we read the raw image also? |
| |
| bool needRawImage = host.SaveDNGVersion () != 0 && |
| !host.SaveLinearDNG (*this); |
| |
| // Allocate image we are reading. |
| |
| dng_ifd &enhancedIFD = *info.fIFD [info.fEnhancedIndex]; |
| |
| fStage3Image.Reset (host.Make_dng_image (enhancedIFD.Bounds (), |
| enhancedIFD.fSamplesPerPixel, |
| enhancedIFD.PixelType ())); |
| |
| // Do we need to keep the lossy compressed data? |
| |
| if (needRawImage) |
| { |
| |
| fEnhancedLossyCompressedImage.Reset (KeepLossyCompressedImage (host, |
| enhancedIFD)); |
| |
| } |
| |
| // Read the image. |
| |
| enhancedIFD.ReadImage (host, |
| stream, |
| *fStage3Image.Get (), |
| fEnhancedLossyCompressedImage.Get ()); |
| |
| // Stage 3 black level. |
| |
| SetStage3BlackLevel ((uint16) Round_uint32 (enhancedIFD.fBlackLevel [0] [0] [0])); |
| |
| // If we are saving as a linear DNG, the saved profile should |
| // match the profile for the stage3 image. |
| |
| if (host.SaveDNGVersion () != 0 && host.SaveLinearDNG (*this)) |
| { |
| |
| AdjustProfileForStage3 (); |
| |
| } |
| |
| // Read in raw image data if required. |
| |
| if (needRawImage) |
| { |
| |
| ReadStage1Image (host, |
| stream, |
| info); |
| |
| fRawImage.Reset (fStage1Image.Release ()); |
| |
| } |
| |
| // Else we can discard the information specific to the raw IFD. |
| |
| else |
| { |
| |
| // We at least need to know about lens opcodes, so we read the |
| // opcodes and then discard them. |
| |
| ReadOpcodeLists (host, |
| stream, |
| info); |
| |
| ClearLinearizationInfo (); |
| |
| ClearMosaicInfo (); |
| |
| fOpcodeList1.Clear (); |
| fOpcodeList2.Clear (); |
| fOpcodeList3.Clear (); |
| |
| fRawImageDigest .Clear (); |
| fNewRawImageDigest.Clear (); |
| |
| fRawDefaultCropSizeH.Clear (); |
| fRawDefaultCropSizeV.Clear (); |
| |
| fRawDefaultCropOriginH.Clear (); |
| fRawDefaultCropOriginV.Clear (); |
| |
| fRawDefaultScaleH.Clear (); |
| fRawDefaultScaleV.Clear (); |
| |
| fRawBestQualityScale.Clear (); |
| |
| fRawBaselineSharpness .Clear (); |
| fRawNoiseReductionApplied.Clear (); |
| |
| fRawNoiseProfile = dng_noise_profile (); |
| |
| if (fRawDataUniqueID.IsValid ()) |
| { |
| fRawDataUniqueID = RawDataUniqueID (); |
| } |
| |
| fEnhanceParams.Clear (); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetStage1Image (AutoPtr<dng_image> &image) |
| { |
| |
| fStage1Image.Reset (image.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ClearStage1Image () |
| { |
| |
| fStage1Image.Reset (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetStage2Image (AutoPtr<dng_image> &image) |
| { |
| |
| fStage2Image.Reset (image.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetStage3Image (AutoPtr<dng_image> &image) |
| { |
| |
| fStage3Image.Reset (image.Release ()); |
| |
| SetFloatingPoint (fStage3Image.Get () && |
| (fStage3Image->PixelType () == ttFloat)); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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, |
| *this, |
| 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 stage3 image. |
| |
| if (TransparencyMask () && host.SaveDNGVersion () < dngVersion_1_4_0_0) |
| { |
| fRawImageStage = rawImageStagePostOpcode3; |
| } |
| |
| else if (fOpcodeList3.MinVersion (false) > host.SaveDNGVersion () || |
| fOpcodeList3.AlwaysApply ()) |
| { |
| fRawImageStage = rawImageStagePostOpcode3; |
| } |
| |
| // If we are not doing a full resolution read, then always save the DNG |
| // from the processed stage 3 image. |
| |
| else if (host.PreferredSize ()) |
| { |
| 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 (NeedLossyCompressMosaicJXL (host)) |
| { |
| 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; |
| |
| } |
| |
| } |
| |
| } |
| |
| // If the host is requesting a negative read for fast conversion to |
| // DNG, then check whether we can actually do a fast interpolation or |
| // not. For now, keep the logic simple. If the raw image stage is the |
| // pre-opcode stage 1 image (original), then proceed with trying a |
| // fast/downsampled interpolation when building the stage 3 image. |
| // Otherwise, turn off the attempted optimization. |
| |
| if (host.ForFastSaveToDNG () && |
| (fRawImageStage > rawImageStagePreOpcode1)) |
| { |
| |
| // Disable/revert the optimization attempt, and do a normal |
| // interpolation when building the stage 3 image. |
| |
| host.SetForFastSaveToDNG (false, 0); |
| |
| } |
| |
| } |
| |
| // Grab clone of raw image if required. |
| |
| if (fRawImageStage == rawImageStagePreOpcode1) |
| { |
| |
| fRawImage.Reset (fStage1Image->Clone ()); |
| |
| if (fTransparencyMask.Get ()) |
| { |
| fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); |
| } |
| |
| if (fDepthMap.Get ()) |
| { |
| fRawDepthMap.Reset (fDepthMap->Clone ()); |
| } |
| |
| // If we choose to resize semantic masks to match the stage 3 image, |
| // then this would be a good time to grab a copy of the semantic masks |
| // and store them separately. |
| |
| // ... |
| |
| } |
| |
| 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 lossy compressed image is no longer valid. |
| |
| ClearRawLossyCompressedImage (); |
| |
| // Nor is the digest of the raw lossy compressed data. |
| |
| ClearRawLossyCompressedImageDigest (); |
| |
| // 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 ()); |
| } |
| |
| if (fDepthMap.Get ()) |
| { |
| fRawDepthMap.Reset (fDepthMap->Clone ()); |
| } |
| |
| // If we choose to resize semantic masks to match the stage 3 image, |
| // then this would be a good time to grab a copy of the semantic masks |
| // and store them separately. |
| |
| // ... |
| |
| } |
| |
| // 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 ()); |
| |
| fRawImageBlackLevel = fStage3BlackLevel; |
| |
| if (fTransparencyMask.Get ()) |
| { |
| fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); |
| } |
| |
| if (fDepthMap.Get ()) |
| { |
| fRawDepthMap.Reset (fDepthMap->Clone ()); |
| } |
| |
| // If we choose to resize semantic masks to match the stage 3 image, |
| // then this would be a good time to grab a copy of the semantic masks |
| // and store them separately. |
| |
| // ... |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::DoInterpolateStage3 (dng_host &host, |
| int32 srcPlane, |
| dng_matrix *scaleTransforms) |
| { |
| |
| dng_image &stage2 = *fStage2Image.Get (); |
| |
| dng_mosaic_info &info = *fMosaicInfo.Get (); |
| |
| dng_point downScale; |
| |
| const bool fastSaveToDNG = host.ForFastSaveToDNG (); |
| |
| const uint32 fastSaveSize = host.FastSaveToDNGSize (); |
| |
| if (fastSaveToDNG && (fastSaveSize > 0)) |
| { |
| |
| downScale = info.DownScale (host.MinimumSize (), |
| host.FastSaveToDNGSize (), |
| host.CropFactor ()); |
| |
| } |
| |
| else |
| { |
| |
| 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, |
| scaleTransforms); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| // Interpolate and merge a multi-channel CFA image. |
| |
| void dng_negative::DoMergeStage3 (dng_host &host, |
| dng_matrix *scaleTransforms) |
| { |
| |
| // The DNG SDK does not provide multi-channel CFA image merging code. |
| // It just grabs the first channel and uses that. |
| |
| DoInterpolateStage3 (host, 0, scaleTransforms); |
| |
| // 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, |
| dng_matrix *scaleTransforms) |
| { |
| |
| // 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, |
| scaleTransforms); |
| |
| } |
| |
| // Else do a single channel interpolation. |
| |
| else |
| { |
| |
| DoInterpolateStage3 (host, |
| srcPlane, |
| scaleTransforms); |
| |
| } |
| |
| // 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, NULL); |
| |
| // Delete the stage2 image now that we have computed the stage 3 image, |
| // unless the host wants to preserve it. |
| |
| if (!host.WantsPreserveStage2 ()) |
| { |
| |
| fStage2Image.Reset (); |
| |
| } |
| |
| // Are we done with the mosaic info? |
| |
| if (fRawImageStage >= rawImageStagePreOpcode3) |
| { |
| |
| // If we're preserving the stage 2 image, also preserve the mosaic |
| // info. |
| |
| if (!host.WantsPreserveStage2 ()) |
| { |
| |
| ClearMosaicInfo (); |
| |
| } |
| |
| } |
| |
| // If we are keeping the raw image, keep the raw default |
| // crop and scale information. |
| |
| if (fRawImageStage < rawImageStagePreOpcode3) |
| { |
| |
| SetRawDefaultCrop (); |
| SetRawDefaultScale (); |
| SetRawBestQualityScale (); |
| |
| } |
| |
| // To support saving linear DNG files, to need to account for |
| // and upscaling during interpolation. |
| |
| if (fRawToFullScaleH > 1.0) |
| { |
| |
| fDefaultCropSizeH .ScaleBy (fRawToFullScaleH); |
| fDefaultCropOriginH.ScaleBy (fRawToFullScaleH); |
| fDefaultScaleH .ScaleBy (1.0 / fRawToFullScaleH); |
| |
| fRawToFullScaleH = 1.0; |
| |
| } |
| |
| if (fRawToFullScaleV > 1.0) |
| { |
| |
| fDefaultCropSizeV .ScaleBy (fRawToFullScaleV); |
| fDefaultCropOriginV.ScaleBy (fRawToFullScaleV); |
| fDefaultScaleV .ScaleBy (1.0 / fRawToFullScaleV); |
| |
| fRawToFullScaleV = 1.0; |
| |
| } |
| |
| // Resample the transparency mask if required. |
| |
| ResizeTransparencyToMatchStage3 (host); |
| |
| // Grab clone of raw image if required. |
| |
| if (fRawImageStage == rawImageStagePreOpcode3) |
| { |
| |
| fRawImage.Reset (fStage3Image->Clone ()); |
| |
| fRawImageBlackLevel = fStage3BlackLevel; |
| |
| if (fTransparencyMask.Get ()) |
| { |
| fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); |
| } |
| |
| if (fDepthMap.Get ()) |
| { |
| fRawDepthMap.Reset (fDepthMap->Clone ()); |
| } |
| |
| // If we choose to resize semantic masks to match the stage 3 image, |
| // then this would be a good time to grab a copy of the semantic masks |
| // and store them separately. |
| |
| // ... |
| |
| } |
| |
| // Process opcode list 3. |
| |
| host.ApplyOpcodeList (fOpcodeList3, *this, fStage3Image); |
| |
| // See if we are done with the opcode list 3. |
| |
| if (fRawImageStage > rawImageStagePreOpcode3) |
| { |
| |
| // Currently re-use the same flag for preserving the opcode list. |
| |
| if (!host.WantsPreserveStage2 ()) |
| { |
| |
| 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); |
| |
| // Depth maps are often lower resolution than the main image, |
| // so make sure we upsample if required. |
| |
| ResizeDepthToMatchStage3 (host); |
| |
| // For now, do not resize semantic masks to match stage 3 image. |
| |
| // Update Floating Point flag. |
| |
| SetFloatingPoint (fStage3Image->PixelType () == ttFloat); |
| |
| // 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_base_proxy_curve |
| { |
| |
| public: |
| |
| virtual ~dng_base_proxy_curve () |
| { |
| } |
| |
| virtual real64 EvaluateScene (real64 x) const = 0; |
| |
| virtual real64 EvaluateOutput (real64 x) const = 0; |
| |
| virtual real64 SceneSlope () const = 0; |
| |
| virtual real64 OutputSlope () const = 0; |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| class dng_jpeg_proxy_curve : public dng_base_proxy_curve |
| { |
| |
| // RESEARCH: Instead of using a constant slope, consider using a family of |
| // slopes ranging from the original one (1/16) to a limit of 1/128, |
| // depending on the histogram distribution. |
| |
| private: |
| |
| static constexpr real64 kSceneProxyCurveSlope = 1.0 / 128.0; |
| |
| static constexpr real64 kOutputProxyCurveSlope = 1.0 / 16.0; |
| |
| public: |
| |
| virtual real64 EvaluateScene (real64 x) const override |
| { |
| |
| // The following code evaluates the inverse of: |
| // |
| // f (x) = (s * x) + ((1 - s) * x^3) |
| // |
| // where s is the slope of the function at the origin (x==0). |
| |
| static constexpr real64 s = kSceneProxyCurveSlope; |
| |
| static const real64 k0 = pow (2.0, 1.0 / 3.0); |
| |
| static constexpr real64 k1 = 108.0 * s * s * s * (1.0 - s) * (1.0 - s) * (1.0 - s); |
| |
| real64 k2 = (27.0 * x) - (54.0 * s * x) + (27.0 * x * s * s); |
| |
| real64 k3 = pow (k2 + sqrt (k1 + k2 * k2), 1.0 / 3.0); |
| |
| real64 y = (k3 / (3.0 * k0 * (1.0 - s))) - (k0 * s / k3); |
| |
| y = Pin_real64 (0.0, y, 1.0); |
| |
| #if 0 |
| |
| DNG_ASSERT (Abs_real64 (x - (kSceneProxyCurveSlope * y + |
| (1.0 - kSceneProxyCurveSlope) * y * y * y)) < 0.0000001, |
| "SceneProxyCurve round trip error"); |
| |
| #endif |
| |
| return y; |
| |
| } |
| |
| virtual real64 EvaluateOutput (real64 x) const override |
| { |
| |
| // DNG_ASSERT (kOutputProxyCurveSlope == 1.0 / 16.0, |
| // "OutputProxyCurve unexpected slope"); |
| |
| real64 y = (sqrt (960.0 * x + 1.0) - 1.0) / 30.0; |
| |
| #if 0 |
| |
| DNG_ASSERT (Abs_real64 (x - (kOutputProxyCurveSlope * y + |
| (1.0 - kOutputProxyCurveSlope) * y * y)) < 0.0000001, |
| "OutputProxyCurve round trip error"); |
| |
| #endif |
| |
| return y; |
| |
| } |
| |
| virtual real64 SceneSlope () const override |
| { |
| return kSceneProxyCurveSlope; |
| } |
| |
| virtual real64 OutputSlope () const override |
| { |
| return kOutputProxyCurveSlope; |
| } |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| // This curve is intended for integer data, not floating-point data. |
| |
| class dng_jxl_proxy_curve : public dng_base_proxy_curve |
| { |
| |
| #if 1 |
| |
| // Non-linear curve that is similar to what we use for JPEGs. Bigger |
| // files, but much better shadow response. |
| |
| private: |
| |
| static constexpr real64 kSceneProxyCurveSlope = 1.0 / 16.0; |
| |
| static constexpr real64 kOutputProxyCurveSlope = 1.0 / 16.0; |
| |
| public: |
| |
| virtual real64 EvaluateScene (real64 x) const override |
| { |
| |
| // The following code evaluates the inverse of: |
| // |
| // f (x) = (s * x) + ((1 - s) * x^3) |
| // |
| // where s is the slope of the function at the origin (x==0). |
| |
| static constexpr real64 s = kSceneProxyCurveSlope; |
| |
| static const real64 k0 = pow (2.0, 1.0 / 3.0); |
| |
| static constexpr real64 k1 = 108.0 * s * s * s * (1.0 - s) * (1.0 - s) * (1.0 - s); |
| |
| real64 k2 = (27.0 * x) - (54.0 * s * x) + (27.0 * x * s * s); |
| |
| real64 k3 = pow (k2 + sqrt (k1 + k2 * k2), 1.0 / 3.0); |
| |
| real64 y = (k3 / (3.0 * k0 * (1.0 - s))) - (k0 * s / k3); |
| |
| y = Pin_real64 (0.0, y, 1.0); |
| |
| #if 0 |
| |
| DNG_ASSERT (Abs_real64 (x - (kSceneProxyCurveSlope * y + |
| (1.0 - kSceneProxyCurveSlope) * y * y * y)) < 0.0000001, |
| "SceneProxyCurve round trip error"); |
| |
| #endif |
| |
| return y; |
| |
| } |
| |
| virtual real64 EvaluateOutput (real64 x) const override |
| { |
| |
| // DNG_ASSERT (kOutputProxyCurveSlope == 1.0 / 16.0, |
| // "OutputProxyCurve unexpected slope"); |
| |
| real64 y = (sqrt (960.0 * x + 1.0) - 1.0) / 30.0; |
| |
| #if 0 |
| |
| DNG_ASSERT (Abs_real64 (x - (kOutputProxyCurveSlope * y + |
| (1.0 - kOutputProxyCurveSlope) * y * y)) < 0.0000001, |
| "OutputProxyCurve round trip error"); |
| |
| #endif |
| |
| return y; |
| |
| } |
| |
| virtual real64 SceneSlope () const override |
| { |
| return kSceneProxyCurveSlope; |
| } |
| |
| virtual real64 OutputSlope () const override |
| { |
| return kOutputProxyCurveSlope; |
| } |
| |
| #else |
| |
| // No curve. This causes JPEG XL to overly compress in the shadows. |
| |
| public: |
| |
| virtual real64 EvaluateScene (real64 x) const override |
| { |
| return x; |
| } |
| |
| virtual real64 EvaluateOutput (real64 x) const override |
| { |
| return x; |
| } |
| |
| virtual real64 SceneSlope () const override |
| { |
| return 1.0; |
| } |
| |
| virtual real64 OutputSlope () const override |
| { |
| return 1.0; |
| } |
| |
| #endif // new vs old method |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| class dng_gamma_encode_proxy : public dng_1d_function |
| { |
| |
| private: |
| |
| real64 fLower; |
| real64 fUpper; |
| |
| bool fIsSceneReferred; |
| |
| real64 fStage3BlackLevel; |
| |
| real64 fBlackLevel; |
| |
| const dng_base_proxy_curve &fBaseCurve; |
| |
| public: |
| |
| dng_gamma_encode_proxy (real64 lower, |
| real64 upper, |
| bool isSceneReferred, |
| real64 stage3BlackLevel, |
| real64 blackLevel, |
| const dng_base_proxy_curve &baseCurve) |
| |
| : fLower (lower) |
| , fUpper (upper) |
| , fIsSceneReferred (isSceneReferred) |
| , fStage3BlackLevel (stage3BlackLevel) |
| , fBlackLevel (blackLevel) |
| , fBaseCurve (baseCurve) |
| |
| { |
| |
| } |
| |
| virtual real64 Evaluate (real64 x) const |
| { |
| |
| real64 y; |
| |
| if (fIsSceneReferred) |
| { |
| |
| if (fLower < fStage3BlackLevel) |
| { |
| |
| x = Pin_real64 (-1.0, |
| (x - fStage3BlackLevel) / (fUpper - fStage3BlackLevel), |
| 1.0); |
| |
| if (x >= 0.0) |
| { |
| |
| y = fBaseCurve.EvaluateScene (x); |
| |
| } |
| |
| else |
| { |
| |
| y = -fBaseCurve.EvaluateScene (-x); |
| |
| } |
| |
| y = Pin_real64 (0.0, y * (1.0 - fBlackLevel) + fBlackLevel, 1.0); |
| |
| } |
| |
| else |
| { |
| |
| x = Pin_real64 (0.0, (x - fLower) / (fUpper - fLower), 1.0); |
| |
| y = fBaseCurve.EvaluateScene (x); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| x = Pin_real64 (0.0, (x - fLower) / (fUpper - fLower), 1.0); |
| |
| y = fBaseCurve.EvaluateOutput (x); |
| |
| } |
| |
| return y; |
| |
| } |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| class dng_encode_proxy_task: public dng_area_task, |
| private dng_uncopyable |
| { |
| |
| 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 *lower, |
| const real64 *upper, |
| bool isSceneReferred, |
| real64 stage3BlackLevel, |
| real64 *blackLevel, |
| real64 whiteLevel, |
| const dng_base_proxy_curve &baseCurve); |
| |
| 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); |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| dng_encode_proxy_task::dng_encode_proxy_task (dng_host &host, |
| const dng_image &srcImage, |
| dng_image &dstImage, |
| const real64 *lower, |
| const real64 *upper, |
| bool isSceneReferred, |
| real64 stage3BlackLevel, |
| real64 *blackLevel, |
| real64 whiteLevel, |
| const dng_base_proxy_curve &baseCurve) |
| |
| : dng_area_task ("dng_encode_proxy_task") |
| |
| , fSrcImage (srcImage) |
| , fDstImage (dstImage) |
| |
| { |
| |
| for (uint32 plane = 0; plane < fSrcImage.Planes (); plane++) |
| { |
| |
| fTable16 [plane] . Reset (host.Allocate (0x10000 * sizeof (uint16))); |
| |
| const real64 normBlackLevel = blackLevel [plane] / whiteLevel; |
| |
| dng_gamma_encode_proxy gamma (lower [plane], |
| upper [plane], |
| isSceneReferred, |
| stage3BlackLevel, |
| normBlackLevel, |
| baseCurve); |
| |
| // Compute fast approximation of encoding table. |
| |
| dng_1d_table table32; |
| |
| table32.Initialize (host.Allocator (), gamma); |
| |
| table32.Expand16 (fTable16 [plane]->Buffer_uint16 ()); |
| |
| // The gamma curve has some fairly high curvature near |
| // the black point, and the above approximation can actually |
| // change results. So use exact math near the black point. |
| // Still very fast, since we are only computing a small |
| // fraction of the range exactly. |
| |
| { |
| |
| const int32 kHighResRadius = 1024; |
| |
| uint32 zeroPt = Round_uint32 (stage3BlackLevel * 65535.0); |
| |
| uint32 highResLower = Max_int32 (0 , zeroPt - kHighResRadius); |
| uint32 highResUpper = Min_int32 (0x10000, zeroPt + kHighResRadius); |
| |
| for (uint32 j = highResLower; j < highResUpper; j++) |
| { |
| |
| real64 x = j * (1.0 / 65535.0); |
| |
| real64 y = gamma.Evaluate (x); |
| |
| uint16 z = Pin_uint16 (Round_int32 (y * 65535.0)); |
| |
| fTable16 [plane]->Buffer_uint16 () [j] = z; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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; |
| |
| if (fDstImage.PixelSize () == 2) |
| { |
| |
| // 16-bit path. |
| |
| 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); |
| |
| uint16 *dPtr = dstBuffer.DirtyPixel_uint16 (row, tile.l, plane); |
| |
| for (int32 col = tile.l; col < tile.r; col++) |
| { |
| |
| *dPtr = map [*sPtr]; |
| |
| sPtr += sColStep; |
| dPtr += dColStep; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| // 8-bit path. |
| |
| 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++) |
| { |
| |
| // BULLSHIT: "Noise_Planes_Issue" |
| // For each pixel we are applying the same noise to each plane. |
| // This does not seem ideal at it will shift all planes equally for each pixel. |
| // Is this really what we want? Maybe it helps to preseve hue slightly? |
| // Not sure if this was 100% intentional or not. |
| |
| 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; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /******************************************************************************/ |
| |
| bool dng_negative::SupportsPreservedBlackLevels (dng_host & /* host */) |
| { |
| |
| return false; |
| |
| } |
| |
| /******************************************************************************/ |
| |
| dng_image * EncodeImageForCompression (dng_host &host, |
| const dng_image &srcImage, |
| const dng_rect &activeArea, |
| const bool isSceneReferred, |
| const bool use16bit, |
| const real64 srcBlackLevel, |
| real64 *dstBlackLevel, |
| dng_opcode_list &opcodeList) |
| { |
| |
| if (srcImage.PixelType () != ttShort) |
| { |
| return nullptr; |
| } |
| |
| real64 lower [kMaxColorPlanes]; |
| real64 upper [kMaxColorPlanes]; |
| |
| { |
| |
| const real64 kClipFraction = 0.00001; |
| |
| uint64 pixels = (uint64) activeArea.H () * |
| (uint64) activeArea.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, |
| activeArea, |
| hist, |
| 65535, |
| plane); |
| |
| uint32 total = 0; |
| |
| uint32 upperIndex = 65535; |
| |
| while (total + hist [upperIndex] <= limit && upperIndex > 255) |
| { |
| |
| total += hist [upperIndex]; |
| |
| upperIndex--; |
| |
| } |
| |
| total = 0; |
| |
| uint32 lowerIndex = 0; |
| |
| while (total + hist [lowerIndex] <= limit && lowerIndex < upperIndex - 255) |
| { |
| |
| total += hist [lowerIndex]; |
| |
| lowerIndex++; |
| |
| } |
| |
| lower [plane] = lowerIndex / 65535.0; |
| upper [plane] = upperIndex / 65535.0; |
| |
| } |
| |
| } |
| |
| AutoPtr<dng_base_proxy_curve> baseCurve; |
| |
| if (use16bit) |
| baseCurve.Reset (new dng_jxl_proxy_curve); |
| else |
| baseCurve.Reset (new dng_jpeg_proxy_curve); |
| |
| for (uint32 n = 0; n < kMaxColorPlanes; n++) |
| { |
| dstBlackLevel [n] = 0.0; |
| } |
| |
| const uint32 whiteLevel = use16bit ? 65535 : 255; |
| |
| const real64 whiteLevel64 = real64 (whiteLevel); |
| |
| if (isSceneReferred && (srcBlackLevel > 0.0)) |
| { |
| |
| for (uint32 plane = 0; plane < srcImage.Planes (); plane++) |
| { |
| |
| if (lower [plane] < srcBlackLevel) |
| { |
| |
| upper [plane] = Max_real64 (upper [plane], |
| srcBlackLevel + |
| (srcBlackLevel - lower [plane]) * |
| (1.0 / kMaxStage3BlackLevelNormalized - 1.0)); |
| |
| upper [plane] = Min_real64 (upper [plane], 1.0); |
| |
| real64 negRange = |
| baseCurve->EvaluateScene ((srcBlackLevel - lower [plane]) / |
| (upper [plane] - srcBlackLevel)); |
| |
| real64 outBlack = negRange / (1.0 + negRange); |
| |
| dstBlackLevel [plane] = Min_real64 (kMaxStage3BlackLevelNormalized * whiteLevel64, |
| ceil (outBlack * whiteLevel64)); |
| |
| } |
| |
| } |
| |
| } |
| |
| // Apply the gamma encoding, using dither when downsampling to 8-bit. |
| |
| AutoPtr<dng_image> dstImage (host.Make_dng_image (srcImage.Bounds (), |
| srcImage.Planes (), |
| use16bit ? ttShort : ttByte)); |
| |
| { |
| |
| DNG_REQUIRE (baseCurve.Get (), |
| "missing base curve"); |
| |
| dng_encode_proxy_task task (host, |
| srcImage, |
| *dstImage, |
| lower, |
| upper, |
| isSceneReferred, |
| srcBlackLevel, |
| dstBlackLevel, |
| whiteLevel64, |
| *baseCurve); |
| |
| host.PerformAreaTask (task, |
| srcImage.Bounds ()); |
| |
| } |
| |
| // Add opcodes to undo the gamma encoding. |
| |
| { |
| |
| for (uint32 plane = 0; plane < srcImage.Planes (); plane++) |
| { |
| |
| dng_area_spec areaSpec (dng_rect (activeArea.Size ()), |
| plane); |
| |
| real64 coefficient [4]; |
| |
| coefficient [0] = 0.0; |
| |
| if (isSceneReferred) |
| { |
| coefficient [1] = baseCurve->SceneSlope (); |
| coefficient [2] = 0.0; |
| coefficient [3] = 1.0 - coefficient [1]; |
| } |
| else |
| { |
| coefficient [1] = baseCurve->OutputSlope (); |
| coefficient [2] = 1.0 - coefficient [1]; |
| coefficient [3] = 0.0; |
| } |
| |
| if (lower [plane] < srcBlackLevel) |
| { |
| |
| real64 rescale = (upper [plane] - srcBlackLevel) / (1.0 - srcBlackLevel); |
| |
| coefficient [0] *= rescale; |
| coefficient [1] *= rescale; |
| coefficient [2] *= rescale; |
| coefficient [3] *= rescale; |
| |
| } |
| |
| else |
| { |
| |
| real64 rescale = (upper [plane] - lower [plane]) / (1.0 - srcBlackLevel); |
| |
| coefficient [0] *= rescale; |
| coefficient [1] *= rescale; |
| coefficient [2] *= rescale; |
| coefficient [3] *= rescale; |
| |
| coefficient [0] += (lower [plane] - srcBlackLevel) / (1.0 - srcBlackLevel); |
| |
| } |
| |
| AutoPtr<dng_opcode> opcode (new dng_opcode_MapPolynomial (areaSpec, |
| isSceneReferred ? 3 : 2, |
| coefficient)); |
| |
| opcodeList.Append (opcode); |
| |
| } |
| |
| } |
| |
| return dstImage.Release (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::NeedLossyCompressMosaicJXL (dng_host &host) const |
| { |
| |
| if (!host.LossyMosaicJXL ()) |
| { |
| return false; |
| } |
| |
| if (host.SaveDNGVersion () < dngVersion_1_7_1_0) |
| { |
| return false; |
| } |
| |
| if (!GetMosaicInfo () || |
| !GetMosaicInfo ()->IsColorFilterArray () || |
| GetMosaicInfo ()->fCFAPatternSize.h != 2 || |
| GetMosaicInfo ()->fCFAPatternSize.v != 2) |
| { |
| return false; |
| } |
| |
| if (RawLossyCompressedImage () && |
| RawLossyCompressedImage ()->fCompressionCode == ccJXL && |
| RawLossyCompressedImage ()->fJXLDistance != 0.0f) |
| { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| class dng_lossy_mosaic_task : public dng_area_task |
| , private dng_uncopyable |
| { |
| |
| public: |
| |
| const dng_image &fSrcImage; |
| dng_image &fDstImage; |
| |
| dng_point fOffset; |
| |
| private: |
| |
| enum |
| { |
| kMaxThreads = 4 |
| }; |
| |
| AutoPtr<dng_memory_block> fBuffer [kMaxThreads]; |
| |
| public: |
| |
| dng_lossy_mosaic_task (const dng_image &srcImage, |
| dng_image &dstImage, |
| const dng_point &offset) |
| |
| : dng_area_task ("dng_lossy_mosaic_task") |
| |
| , fSrcImage (srcImage) |
| , fDstImage (dstImage) |
| , fOffset (offset) |
| |
| { |
| |
| fMaxThreads = kMaxThreads; |
| |
| fMaxTileSize = dng_point (512, 512); |
| |
| } |
| |
| dng_rect RepeatingTile1 () const override |
| { |
| return fDstImage.RepeatingTile (); |
| } |
| |
| void Start (uint32 threadCount, |
| const dng_rect &dstArea, |
| const dng_point &tileSize, |
| dng_memory_allocator *allocator, |
| dng_abort_sniffer *sniffer) override; |
| |
| void Process (uint32 threadIndex, |
| const dng_rect &tile, |
| dng_abort_sniffer *sniffer) override; |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| void dng_lossy_mosaic_task::Start (uint32 threadCount, |
| const dng_rect & /* dstArea */, |
| const dng_point &tileSize, |
| dng_memory_allocator *allocator, |
| dng_abort_sniffer * /* sniffer */) |
| { |
| |
| uint32 bufferSize = tileSize.h * |
| tileSize.v * |
| fDstImage.PixelSize (); |
| |
| for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++) |
| { |
| |
| fBuffer [threadIndex].Reset (allocator->Allocate (bufferSize)); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_lossy_mosaic_task::Process (uint32 threadIndex, |
| const dng_rect &tile, |
| dng_abort_sniffer * /* sniffer */) |
| { |
| |
| dng_pixel_buffer dstBuffer; |
| |
| dstBuffer.fArea = tile; |
| dstBuffer.fPlane = 0; |
| dstBuffer.fPlanes = 1; |
| dstBuffer.fPlaneStep = 1; |
| dstBuffer.fColStep = 1; |
| dstBuffer.fRowStep = tile.W (); |
| dstBuffer.fPixelType = fDstImage.PixelType (); |
| dstBuffer.fPixelSize = fDstImage.PixelSize (); |
| dstBuffer.fData = fBuffer [threadIndex]->Buffer (); |
| dstBuffer.fDirty = true; |
| |
| dng_pixel_buffer srcBuffer = dstBuffer; |
| |
| srcBuffer.fArea = srcBuffer.fArea - fOffset; |
| |
| fSrcImage.Get (srcBuffer, |
| dng_image::edge_repeat, |
| 2, |
| 2); |
| |
| fDstImage.Put (dstBuffer); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::LossyCompressMosaicJXL (dng_host &host, |
| dng_image_writer &writer) |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::CompressTransparencyMaskJXL (dng_host &host, |
| dng_image_writer &writer, |
| bool nearLosslessOK) |
| { |
| |
| if (host.SaveDNGVersion () != 0 && |
| host.SaveDNGVersion () < MinBackwardVersionForCompression (ccJXL)) |
| { |
| return; |
| } |
| |
| if (!RawLossyCompressedTransparencyMask () && |
| RawTransparencyMask () != nullptr && |
| SupportsJXL (*RawTransparencyMask ()) && |
| (RawTransparencyMask ()->PixelType () != ttFloat || RawTransparencyMaskBitDepth () == 16)) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| *RawTransparencyMask (), |
| nearLosslessOK ? dng_host::use_case_Transparency |
| : dng_host::use_case_LosslessTransparency, |
| this); |
| |
| fRawLossyCompressedTransparencyMask.Reset (lossyImage.Release ()); |
| |
| ClearRawImageDigest (); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::CompressDepthMapJXL (dng_host &host, |
| dng_image_writer &writer, |
| bool nearLosslessOK) |
| { |
| |
| if (host.SaveDNGVersion () != 0 && |
| host.SaveDNGVersion () < MinBackwardVersionForCompression (ccJXL)) |
| { |
| return; |
| } |
| |
| if (!RawLossyCompressedDepthMap () && |
| RawDepthMap () != nullptr && |
| SupportsJXL (*RawDepthMap ()) && |
| RawDepthMap ()->PixelType () != ttFloat) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| *RawDepthMap (), |
| nearLosslessOK ? dng_host::use_case_Depth |
| : dng_host::use_case_LosslessDepth, |
| this); |
| |
| fRawLossyCompressedDepthMap.Reset (lossyImage.Release ()); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::CompressSemanticMasksJXL (dng_host &host, |
| dng_image_writer &writer, |
| bool nearLosslessOK) |
| { |
| |
| if (host.SaveDNGVersion () != 0 && |
| host.SaveDNGVersion () < MinBackwardVersionForCompression (ccJXL)) |
| { |
| return; |
| } |
| |
| // JXL compress semantic masks, if not already compressed. |
| |
| const uint32 maskCount = NumSemanticMasks (); |
| |
| for (uint32 i = 0; i < maskCount; i++) |
| { |
| |
| auto &mask = fSemanticMasks [i]; |
| |
| if (!mask.fLossyCompressed.get () && |
| SupportsJXL (*mask.fMask) && |
| (mask.fMask->PixelType () != ttFloat || nearLosslessOK)) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| *mask.fMask, |
| nearLosslessOK ? dng_host::use_case_SemanticMask |
| : dng_host::use_case_LosslessSemanticMask, |
| this); |
| |
| mask.fLossyCompressed.reset (lossyImage.Release ()); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::LosslessCompressJXL (dng_host &host, |
| dng_image_writer &writer, |
| bool nearLosslessOK) |
| { |
| |
| if (host.SaveDNGVersion () != 0 && |
| host.SaveDNGVersion () < MinBackwardVersionForCompression (ccJXL)) |
| { |
| return; |
| } |
| |
| // JXL compress main image, if not already compressed. |
| |
| if (!RawLossyCompressedImage ()) |
| { |
| |
| if (GetMosaicInfo () && |
| GetMosaicInfo ()->IsColorFilterArray ()) |
| { |
| |
| if (host.SaveDNGVersion () >= dngVersion_1_7_1_0 && |
| GetMosaicInfo ()->fCFAPatternSize.h >= 2 && |
| GetMosaicInfo ()->fCFAPatternSize.h < (int32) RawImage ().Width () && |
| GetMosaicInfo ()->fCFAPatternSize.v >= 2 && |
| GetMosaicInfo ()->fCFAPatternSize.v < (int32) RawImage ().Height () && |
| RawImage ().Planes () == 1 && |
| RawImage ().PixelType () == ttShort) |
| { |
| |
| // First, apply interleaving to a temporary image. |
| |
| AutoPtr<dng_image> tempImage (host.Make_dng_image (RawImage ().Bounds (), |
| RawImage ().Planes (), |
| RawImage ().PixelType ())); |
| |
| Interleave2D (host, |
| RawImage (), |
| *tempImage, |
| GetMosaicInfo ()->fCFAPatternSize.v, |
| GetMosaicInfo ()->fCFAPatternSize.h, |
| true); |
| |
| // Compress. |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| *tempImage, |
| dng_host::use_case_LosslessMosaic, |
| this); |
| |
| lossyImage->fRowInterleaveFactor = GetMosaicInfo ()->fCFAPatternSize.v; |
| lossyImage->fColumnInterleaveFactor = GetMosaicInfo ()->fCFAPatternSize.h; |
| |
| fRawLossyCompressedImage.Reset (lossyImage.Release ()); |
| |
| ClearRawLossyCompressedImageDigest (); |
| |
| ClearRawImageDigest (); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| if (SupportsJXL (RawImage ()) && |
| (RawImage ().PixelType () != ttFloat || RawFloatBitDepth () == 16)) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| RawImage (), |
| nearLosslessOK ? dng_host::use_case_MainImage |
| : dng_host::use_case_LosslessMainImage, |
| this); |
| |
| fRawLossyCompressedImage.Reset (lossyImage.Release ()); |
| |
| ClearRawLossyCompressedImageDigest (); |
| |
| ClearRawImageDigest (); |
| |
| } |
| |
| } |
| |
| } |
| |
| // JXL compress enhanced image, if not already compressed. |
| |
| if (!EnhancedLossyCompressedImage () && |
| EnhanceParams ().NotEmpty () && |
| &RawImage () != Stage3Image () && |
| SupportsJXL (*Stage3Image ()) && |
| (Stage3Image ()->PixelType () == ttShort || nearLosslessOK)) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| *RawTransparencyMask (), |
| nearLosslessOK ? dng_host::use_case_EnhancedImage |
| : dng_host::use_case_LosslessEnhancedImage, |
| this); |
| |
| fEnhancedLossyCompressedImage.Reset (lossyImage.Release ()); |
| |
| } |
| |
| // JXL compress transparency mask, if not already compressed. |
| |
| CompressTransparencyMaskJXL (host, |
| writer, |
| nearLosslessOK); |
| |
| // JXL compress depth map, if not already compressed. |
| |
| CompressDepthMapJXL (host, |
| writer, |
| nearLosslessOK); |
| |
| // JXL compress semantic masks, if not already compressed. |
| |
| CompressSemanticMasksJXL (host, |
| writer, |
| nearLosslessOK); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| dng_image * dng_negative::EncodeRawProxy (dng_host &host, |
| const dng_image &srcImage, |
| dng_opcode_list &opcodeList, |
| real64 *blackLevel) const |
| { |
| |
| bool use16bit = SupportsJXL (srcImage) && |
| (!host.SaveDNGVersion () || |
| host.SaveDNGVersion () >= MinBackwardVersionForCompression (ccJXL)); |
| |
| return EncodeImageForCompression (host, |
| srcImage, |
| srcImage.Bounds (), |
| IsSceneReferred (), |
| use16bit, |
| Stage3BlackLevelNormalized (), |
| blackLevel, |
| opcodeList); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_negative::AdjustGainMapForStage3 (dng_host & /* host */) |
| { |
| |
| // For dng_sdk, the stage3 image's color space is always the same as the |
| // raw image's color space, so any gain map does not need adjusting. |
| |
| } |
| |
| /******************************************************************************/ |
| |
| 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 * |
| (uint64) proxySize; |
| } |
| |
| // Is this a (possibly) downsampled proxy? |
| |
| bool nonFullSizeProxy = (proxySize < kMaxImageSide) || |
| (proxyCount < (uint64) kMaxImageSide * |
| (uint64) kMaxImageSide); |
| |
| // Don't need to keep private data around in non-full size proxies. |
| |
| if (nonFullSizeProxy) |
| { |
| |
| ClearMakerNote (); |
| |
| ClearPrivateData (); |
| |
| } |
| |
| // When converting Enhanced images to proxy, make sure to set the |
| // OriginalDefault... fields to reflect the Enhanced image size, not the |
| // standard resolution size. |
| |
| if (RawDefaultCropSizeH ().As_real64 () < DefaultCropSizeH ().As_real64 () && |
| RawDefaultCropSizeV ().As_real64 () < DefaultCropSizeV ().As_real64 ()) |
| { |
| |
| // Clear any existing values. |
| |
| SetOriginalDefaultFinalSize (dng_point ()); |
| |
| SetOriginalBestQualityFinalSize (dng_point ()); |
| |
| SetOriginalDefaultCropSize (dng_urational (), |
| dng_urational ()); |
| |
| // Set the values from scratch. |
| |
| SetDefaultOriginalSizes (); |
| |
| } |
| |
| const bool useJXL = (ColorChannels () == 1 || |
| ColorChannels () == 3) && |
| (!host.SaveDNGVersion () || |
| host.SaveDNGVersion () >= MinBackwardVersionForCompression (ccJXL)); |
| |
| // See if we already have an acceptable proxy raw image. |
| |
| bool rawImageOK = false; |
| |
| real64 pixelAspect = PixelAspectRatio (); |
| |
| bool nonSquarePixels = pixelAspect < 0.99 || |
| pixelAspect > 1.01; |
| |
| if (fRawImage.Get () && |
| fRawImage->Bounds () == DefaultCropArea () && |
| fRawImage->Bounds ().H () <= proxySize && |
| fRawImage->Bounds ().W () <= proxySize && |
| (uint64) fRawImage->Bounds ().H () * |
| (uint64) fRawImage->Bounds ().W () <= proxyCount && |
| fRawToFullScaleH == 1.0 && |
| fRawToFullScaleV == 1.0 && |
| !nonSquarePixels && |
| fEnhanceParams.IsEmpty () && |
| (!GetMosaicInfo () || !GetMosaicInfo ()->IsColorFilterArray ())) |
| { |
| |
| if (fRawImage->PixelType () == ttByte) |
| { |
| |
| rawImageOK = fRawLossyCompressedImage.Get () != nullptr; |
| |
| } |
| |
| else if (fRawImage->PixelType () == ttShort) |
| { |
| |
| rawImageOK = fRawLossyCompressedImage.Get () != nullptr; |
| |
| } |
| |
| else if (fRawImage->PixelType () == ttFloat) |
| { |
| |
| if (RawFloatBitDepth () == 16) |
| { |
| |
| if (useJXL) |
| { |
| rawImageOK = fRawLossyCompressedImage.Get () != nullptr; |
| } |
| |
| else |
| { |
| rawImageOK = true; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // Even if we already used lossy JPEG to encode the raw image, we should |
| // still recompress the data as JXL if allowed for the size savings. |
| |
| if (rawImageOK && |
| useJXL && |
| fRawLossyCompressedImage.Get () && |
| fRawLossyCompressedImage->fCompressionCode != ccJXL) |
| { |
| |
| rawImageOK = false; |
| |
| } |
| |
| // If the raw lossy compressed image is known to be lossless JXL, then |
| // we should not use it for a lossy proxy. |
| |
| if (RawLossyCompressedImage () && |
| RawLossyCompressedImage ()->fCompressionCode == ccJXL && |
| RawLossyCompressedImage ()->fJXLDistance == 0.0f) |
| { |
| |
| rawImageOK = false; |
| |
| } |
| |
| if (!rawImageOK) |
| { |
| |
| // Adjust for any color matrix difference between the |
| // raw image and the stage3 image. |
| |
| AdjustGainMapForStage3 (host); |
| |
| AdjustProfileForStage3 (); |
| |
| // Clear any grabbed raw image, since we are going to start |
| // building the proxy with the stage3 image. |
| |
| fRawImage.Reset (); |
| |
| fRawDefaultScaleH.Clear (); |
| fRawDefaultScaleV.Clear (); |
| |
| fRawBestQualityScale.Clear (); |
| |
| fRawDefaultCropSizeH.Clear (); |
| fRawDefaultCropSizeV.Clear (); |
| |
| fRawDefaultCropOriginH.Clear (); |
| fRawDefaultCropOriginV.Clear (); |
| |
| fRawImageBlackLevel = 0; |
| |
| ClearRawLossyCompressedImage (); |
| |
| SetRawFloatBitDepth (0); |
| |
| ClearLinearizationInfo (); |
| |
| ClearMosaicInfo (); |
| |
| fOpcodeList1.Clear (); |
| fOpcodeList2.Clear (); |
| fOpcodeList3.Clear (); |
| |
| ClearRawImageDigest (); |
| |
| ClearRawLossyCompressedImageDigest (); |
| |
| // Discard the enhanced information since discarded |
| // its source. |
| |
| fEnhanceParams.Clear (); |
| |
| fEnhancedLossyCompressedImage.Reset (); |
| |
| } |
| |
| // Trim off extra pixels outside the default crop area. |
| |
| const dng_rect defaultCropArea = DefaultCropArea (); |
| |
| const dng_rect originalStage3Bounds = Stage3Image ()->Bounds (); |
| |
| if (!rawImageOK) |
| { |
| |
| if (originalStage3Bounds != defaultCropArea) |
| { |
| |
| const dng_rect s3bounds = originalStage3Bounds; |
| |
| fStage3Image->Trim (defaultCropArea); |
| |
| if (fTransparencyMask.Get ()) |
| { |
| fTransparencyMask->Trim (defaultCropArea); |
| fRawTransparencyMask.Reset (); |
| fRawLossyCompressedTransparencyMask.Reset (); |
| } |
| |
| if (fDepthMap.Get ()) |
| { |
| fDepthMap->Trim (defaultCropArea); |
| fRawDepthMap.Reset (); |
| fRawLossyCompressedDepthMap.Reset (); |
| } |
| |
| // Adjust origin and spacing of profile gain table map. |
| |
| if (HasProfileGainTableMap ()) |
| { |
| |
| const auto &gainTableMap = ProfileGainTableMap (); |
| |
| // Adjust origin. |
| |
| const dng_point_real64 &oldOrigin = gainTableMap.Origin (); |
| |
| dng_point_real64 originPix |
| (Lerp_real64 (s3bounds.t, s3bounds.b, oldOrigin.v), |
| Lerp_real64 (s3bounds.l, s3bounds.r, oldOrigin.h)); |
| |
| dng_point_real64 newOrigin |
| ((originPix.v - defaultCropArea.t) / defaultCropArea.H (), |
| (originPix.h - defaultCropArea.l) / defaultCropArea.W ()); |
| |
| // Adjust spacing. |
| |
| const dng_point_real64 &oldSpacing = gainTableMap.Spacing (); |
| |
| dng_point_real64 newSpacing = oldSpacing; |
| |
| const dng_point &points = gainTableMap.Points (); |
| |
| if (points.h > 1) |
| { |
| |
| newSpacing.h *= ((real64) s3bounds.W () / |
| (real64) defaultCropArea.W ()); |
| |
| } |
| |
| if (points.v > 1) |
| { |
| |
| newSpacing.v *= ((real64) s3bounds.H () / |
| (real64) defaultCropArea.H ()); |
| |
| } |
| |
| // Deal with original buffer. |
| |
| // We want to convert the gain map to 8-bit to save sapce. |
| |
| AutoPtr<dng_memory_block> originalBuffer; |
| |
| real32 gainMin = gainTableMap.GainMin (); |
| real32 gainMax = gainTableMap.GainMax (); |
| |
| // If original buffer is present and already 8-bit, then no change |
| // is needed. Just make a copy to hand off to the new gain table |
| // map. |
| |
| if (gainTableMap.IsUint8 () && |
| gainTableMap.HasOriginalBuffer ()) |
| { |
| |
| originalBuffer.Reset |
| (gainTableMap.OriginalBuffer ()->Clone (host.Allocator ())); |
| |
| } |
| |
| // Otherwise, we need to convert to 8-bit. Scan the fp32 values |
| // for the min & max gains. |
| |
| else |
| { |
| |
| const real32 *ptr = gainTableMap.Block ()->Buffer_real32 (); |
| |
| uint32 entries = (gainTableMap.DataStorageBytes () / |
| gainTableMap.BytesPerEntry ()); |
| |
| for (uint32 i = 0; i < entries; i++) |
| { |
| gainMin = Min_real32 (gainMin, ptr [i]); |
| gainMax = Max_real32 (gainMax, ptr [i]); |
| } |
| |
| } |
| |
| // Make the new gain table map. |
| |
| AutoPtr<dng_gain_table_map> newMap |
| (new dng_gain_table_map (host.Allocator (), |
| gainTableMap.Points (), |
| newSpacing, |
| newOrigin, |
| gainTableMap.NumTablePoints (), |
| gainTableMap.MapInputWeights (), |
| 0, // store as uint8 |
| gainTableMap.Gamma (), |
| gainMin, |
| gainMax)); |
| |
| // Copy over fp32 points. |
| |
| memcpy (newMap ->Block ()->Buffer_real32 (), |
| gainTableMap.Block ()->Buffer_real32 (), |
| (size_t) gainTableMap.SampleBytes ()); |
| |
| // Copy over original buffer, if any. |
| |
| if (originalBuffer.Get ()) |
| newMap->SetOriginalBuffer (originalBuffer); |
| |
| // Replace the existing map. |
| |
| SetProfileGainTableMap (newMap); |
| |
| } |
| |
| fDefaultCropOriginH = dng_urational (0, 1); |
| fDefaultCropOriginV = dng_urational (0, 1); |
| |
| } |
| |
| // Figure out the requested proxy pixel size. |
| |
| real64 aspectRatio = BaseAspectRatio (); |
| |
| 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 || nonSquarePixels) |
| { |
| |
| 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; |
| |
| } |
| |
| // If there is still a raw to full scale factor, we need to |
| // remove it and adjust the crop coordinates. |
| |
| else if (fRawToFullScaleH != 1.0 || |
| fRawToFullScaleV != 1.0) |
| { |
| |
| fDefaultCropSizeH = dng_urational (oldSize.h, 1); |
| fDefaultCropSizeV = dng_urational (oldSize.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 (RawImage ().PixelType () == ttFloat && |
| RawFloatBitDepth () != 16) |
| { |
| |
| fRawImage.Reset (host.Make_dng_image (Stage3Image ()->Bounds (), |
| Stage3Image ()->Planes (), |
| ttFloat)); |
| |
| fRawImageBlackLevel = 0; |
| |
| LimitFloatBitDepth (host, |
| *Stage3Image (), |
| *fRawImage, |
| 16, |
| 32768.0f); |
| |
| SetRawFloatBitDepth (16); |
| |
| SetWhiteLevel (32768); |
| |
| } |
| |
| // Lossy compress raw image if required. |
| |
| if (!fRawLossyCompressedImage.Get ()) |
| { |
| |
| if (useJXL) |
| { |
| |
| if (RawImage ().PixelType () == ttFloat) |
| { |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| RawImage (), |
| nonFullSizeProxy ? dng_host::use_case_ProxyImage |
| : dng_host::use_case_EncodedMainImage, |
| this); |
| |
| fRawLossyCompressedImage.Reset (lossyImage.Release ()); |
| |
| } |
| |
| else |
| { |
| |
| real64 blackLevel [kMaxColorPlanes]; |
| |
| fRawImage.Reset (EncodeRawProxy (host, |
| *Stage3Image (), |
| fOpcodeList2, |
| blackLevel)); |
| |
| fRawImageBlackLevel = 0; |
| |
| if (fRawImage.Get ()) |
| { |
| |
| for (uint32 plane = 0; plane < fRawImage->Planes (); plane++) |
| { |
| SetBlackLevel (blackLevel [plane], plane); |
| } |
| |
| } |
| |
| AutoPtr<dng_jxl_image> lossyImage (new dng_jxl_image); |
| |
| lossyImage->Encode (host, |
| writer, |
| RawImage (), |
| nonFullSizeProxy ? dng_host::use_case_ProxyImage |
| : dng_host::use_case_EncodedMainImage, |
| this); |
| |
| fRawLossyCompressedImage.Reset (lossyImage.Release ()); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| if (RawImage ().PixelType () == ttShort) |
| { |
| |
| real64 blackLevel [kMaxColorPlanes]; |
| |
| fRawImage.Reset (EncodeRawProxy (host, |
| *Stage3Image (), |
| fOpcodeList2, |
| blackLevel)); |
| |
| fRawImageBlackLevel = 0; |
| |
| if (fRawImage.Get ()) |
| { |
| |
| SetWhiteLevel (255); |
| |
| for (uint32 plane = 0; plane < fRawImage->Planes (); plane++) |
| { |
| SetBlackLevel (blackLevel [plane], plane); |
| } |
| |
| } |
| |
| } |
| |
| // Compute JPEG compressed version. |
| |
| if (RawImage ().PixelType () == ttByte) |
| { |
| |
| AutoPtr<dng_jpeg_image> jpegImage (new dng_jpeg_image); |
| |
| jpegImage->Encode (host, |
| *this, |
| writer, |
| RawImage ()); |
| |
| fRawLossyCompressedImage.Reset (jpegImage.Release ()); |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| // Deal with transparency mask. |
| |
| if (TransparencyMask ()) |
| { |
| |
| const bool convertTo8Bit = true; |
| |
| ResizeTransparencyToMatchStage3 (host, convertTo8Bit); |
| |
| if (fRawTransparencyMask.Get ()) |
| { |
| |
| if (fRawTransparencyMask->Bounds () != TransparencyMask ()->Bounds () || |
| fRawTransparencyMask->PixelType () != ttByte) |
| { |
| fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); |
| fRawLossyCompressedTransparencyMask.Reset (); |
| } |
| |
| } |
| |
| CompressTransparencyMaskJXL (host, |
| writer, |
| true); |
| |
| } |
| |
| // Deal with depth map. |
| |
| if (DepthMap ()) |
| { |
| |
| ResizeDepthToMatchStage3 (host); |
| |
| if (fRawDepthMap.Get ()) |
| { |
| |
| if (fRawDepthMap->Bounds ().W () > fDepthMap->Bounds ().W () || |
| fRawDepthMap->Bounds ().H () > fDepthMap->Bounds ().H ()) |
| { |
| fRawDepthMap.Reset (); |
| fRawLossyCompressedDepthMap.Reset (); |
| } |
| |
| } |
| |
| CompressDepthMapJXL (host, |
| writer, |
| true); |
| |
| } |
| |
| // Deal with semantic masks. |
| |
| AdjustSemanticMasksForProxy (host, |
| writer, |
| originalStage3Bounds, |
| defaultCropArea); |
| |
| // Recompute the raw data unique ID, since we changed the image data. |
| |
| RecomputeRawDataUniqueID (host); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::IsProxy () const |
| { |
| |
| return (DefaultCropSizeH () != OriginalDefaultCropSizeH ()) && |
| (DefaultCropSizeV () != OriginalDefaultCropSizeV ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ClearTransparencyMask () |
| { |
| |
| fTransparencyMask.Reset (); |
| |
| fRawTransparencyMask.Reset (); |
| |
| fRawTransparencyMaskBitDepth = 0; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| 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]; |
| |
| fTransparencyMask.Reset (host.Make_dng_image (maskIFD.Bounds (), |
| 1, |
| maskIFD.PixelType ())); |
| |
| // Do we need to keep the lossy compressed data? |
| |
| fRawLossyCompressedTransparencyMask.Reset (KeepLossyCompressedImage (host, |
| maskIFD)); |
| |
| // Read the image. |
| |
| maskIFD.ReadImage (host, |
| stream, |
| *fTransparencyMask.Get (), |
| fRawLossyCompressedTransparencyMask.Get ()); |
| |
| // Remember the pixel depth. |
| |
| fRawTransparencyMaskBitDepth = maskIFD.fBitsPerSample [0]; |
| |
| // Remember if transparency mask was lossy compressed. |
| |
| fTransparencyMaskWasLossyCompressed = (maskIFD.fCompression == ccLossyJPEG || |
| maskIFD.fCompression == ccJXL); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ResizeTransparencyToMatchStage3 (dng_host &host, |
| bool convertTo8Bit) |
| { |
| |
| if (TransparencyMask ()) |
| { |
| |
| if ((TransparencyMask ()->Bounds () != fStage3Image->Bounds ()) || |
| (TransparencyMask ()->PixelType () != ttByte && convertTo8Bit)) |
| { |
| |
| if (host.SaveDNGVersion () != dngVersion_None && !convertTo8Bit) |
| { |
| |
| if (!fRawTransparencyMask.Get ()) |
| { |
| fRawTransparencyMask.Reset (fTransparencyMask->Clone ()); |
| } |
| |
| } |
| |
| 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; |
| } |
| |
| else if (convertTo8Bit) |
| { |
| fRawTransparencyMaskBitDepth = 8; |
| } |
| |
| fRawLossyCompressedTransparencyMask.Reset (); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::NeedFlattenTransparency (dng_host & /* host */) |
| { |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::FlattenTransparency (dng_host & /* host */) |
| { |
| |
| ThrowNotYetImplemented ("FlattenTransparency"); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_image * dng_negative::UnflattenedStage3Image () const |
| { |
| |
| if (fUnflattenedStage3Image.Get ()) |
| { |
| |
| return fUnflattenedStage3Image.Get (); |
| |
| } |
| |
| return fStage3Image.Get (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetDepthMap (AutoPtr<dng_image> &depthMap) |
| { |
| |
| fDepthMap.Reset (depthMap.Release ()); |
| |
| SetHasDepthMap (fDepthMap.Get () != NULL); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ReadDepthMap (dng_host &host, |
| dng_stream &stream, |
| dng_info &info) |
| { |
| |
| if (info.fDepthIndex != -1) |
| { |
| |
| // Allocate image we are reading. |
| |
| dng_ifd &depthIFD = *info.fIFD [info.fDepthIndex]; |
| |
| fDepthMap.Reset (host.Make_dng_image (depthIFD.Bounds (), |
| 1, |
| depthIFD.PixelType ())); |
| |
| // Keep lossy compressed depth image? |
| |
| fRawLossyCompressedDepthMap.Reset (KeepLossyCompressedImage (host, |
| depthIFD)); |
| |
| // Read the image. |
| |
| depthIFD.ReadImage (host, |
| stream, |
| *fDepthMap.Get (), |
| fRawLossyCompressedDepthMap.Get ()); |
| |
| SetHasDepthMap (fDepthMap.Get () != NULL); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ResizeDepthToMatchStage3 (dng_host &host) |
| { |
| |
| if (DepthMap ()) |
| { |
| |
| if (DepthMap ()->Bounds () != fStage3Image->Bounds ()) |
| { |
| |
| // If we are upsampling, and have not grabbed the raw depth map |
| // yet, do so now. |
| |
| if (!fRawDepthMap.Get ()) |
| { |
| |
| uint64 imagePixels = fStage3Image->Bounds ().H () * (uint64) |
| fStage3Image->Bounds ().W (); |
| |
| uint64 depthPixels = DepthMap ()->Bounds ().H () * (uint64) |
| DepthMap ()->Bounds ().W (); |
| |
| if (depthPixels < imagePixels) |
| { |
| fRawDepthMap.Reset (fDepthMap->Clone ()); |
| } |
| |
| } |
| |
| AutoPtr<dng_image> newMap (host.Make_dng_image (fStage3Image->Bounds (), |
| 1, |
| DepthMap ()->PixelType ())); |
| |
| host.ResampleImage (*DepthMap (), |
| *newMap); |
| |
| fDepthMap.Reset (newMap.Release ()); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ResizeSemanticMasksToMatchStage3 (dng_host &host) |
| { |
| |
| if (!HasSemanticMask ()) |
| return; |
| |
| if (!fStage3Image.Get ()) |
| return; |
| |
| const dng_rect dstBounds = fStage3Image->Bounds (); |
| |
| for (uint32 i = 0; i < NumSemanticMasks (); i++) |
| { |
| |
| const_dng_image_sptr mask = SemanticMask (i).fMask; |
| |
| if (mask && (mask->Bounds () != dstBounds)) |
| { |
| |
| AutoPtr<dng_image> image |
| (host.Make_dng_image (dstBounds, |
| mask->Planes (), |
| mask->PixelType ())); |
| |
| host.ResampleImage (*mask, |
| *image); |
| |
| fSemanticMasks.at (i).fMask.reset (image.Release ()); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::HasSemanticMask () const |
| { |
| |
| return !fSemanticMasks.empty (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::HasSemanticMask (uint32 index) const |
| { |
| |
| if (((size_t) index) >= fSemanticMasks.size ()) |
| { |
| |
| return false; |
| |
| } |
| |
| return fSemanticMasks [index].fMask != nullptr; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| uint32 dng_negative::NumSemanticMasks () const |
| { |
| |
| return (uint32) fSemanticMasks.size (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_semantic_mask & dng_negative::SemanticMask (uint32 index) const |
| { |
| |
| if (((size_t) index) >= fSemanticMasks.size ()) |
| { |
| |
| ThrowProgramError ("non-existent index in SemanticMask"); |
| |
| } |
| |
| return fSemanticMasks [index]; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| // For now the concept of a "raw" semantic mask is the same as the regular |
| // semantic mask API (original resolution). |
| |
| const dng_semantic_mask & dng_negative::RawSemanticMask (uint32 index) const |
| { |
| |
| return SemanticMask (index); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetSemanticMask (uint32 index, |
| const dng_semantic_mask &mask) |
| { |
| |
| if (!HasSemanticMask (index)) |
| { |
| |
| ThrowProgramError ("non-existent index in SetSemanticMask"); |
| |
| } |
| |
| DNG_REQUIRE (mask.fMask, "missing mask in SetSemanticMask"); |
| |
| fSemanticMasks [index] = mask; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::AppendSemanticMask (const dng_semantic_mask &mask) |
| { |
| |
| DNG_REQUIRE (mask.fMask, "missing mask in AppendSemanticMask"); |
| |
| fSemanticMasks.push_back (mask); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::ReadSemanticMasks (dng_host &host, |
| dng_stream &stream, |
| dng_info &info) |
| { |
| |
| DNG_REQUIRE (info.fSemanticMaskIndices.size () <= kMaxSemanticMasks, |
| "Too many semantic masks"); |
| |
| std::vector<dng_semantic_mask> masks; |
| |
| masks.reserve (info.fSemanticMaskIndices.size ()); |
| |
| for (const uint32 index : info.fSemanticMaskIndices) |
| { |
| |
| dng_ifd &ifd = *info.fIFD.at (index); |
| |
| AutoPtr<dng_image> image (host.Make_dng_image (ifd.Bounds (), |
| 1, |
| ifd.PixelType ())); |
| |
| AutoPtr<dng_lossy_compressed_image> lossyCompressed (KeepLossyCompressedImage (host, ifd)); |
| |
| // Workaround for early files that use lossy JPEG with Compression tag |
| // value 7. |
| |
| #if 1 |
| |
| if (ifd.fCompression == ccJPEG) |
| { |
| |
| // First try to read it directly as Lossless JPEG. |
| |
| bool tryLossyJPEG = false; |
| |
| try |
| { |
| |
| ifd.ReadImage (host, |
| stream, |
| *image); |
| |
| } |
| |
| catch (const dng_exception &e) |
| { |
| |
| // If that doesn't work, then try Lossy JPEG. |
| |
| if (e.ErrorCode () == dng_error_bad_format) |
| { |
| |
| tryLossyJPEG = true; |
| |
| } |
| |
| // Re-throw other exceptions. |
| |
| else |
| { |
| |
| throw; |
| |
| } |
| |
| } |
| |
| // TODO(erichan): JXL support for semantic masks, too? |
| |
| if (tryLossyJPEG) |
| { |
| |
| AutoPtr<dng_ifd> ifdClone (ifd.Clone ()); |
| |
| ifdClone->fCompression = ccLossyJPEG; |
| |
| lossyCompressed.Reset (KeepLossyCompressedImage (host, *ifdClone)); |
| |
| ifdClone->ReadImage (host, |
| stream, |
| *image, |
| lossyCompressed.Get ()); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| ifd.ReadImage (host, |
| stream, |
| *image, |
| lossyCompressed.Get ()); |
| |
| } |
| |
| #else |
| |
| ifd.ReadImage (host, |
| stream, |
| *image, |
| lossyCompressed.Get ()); |
| |
| #endif |
| |
| dng_semantic_mask mask; |
| |
| mask.fName = ifd.fSemanticName; |
| mask.fInstanceID = ifd.fSemanticInstanceID; |
| mask.fXMP = ifd.fSemanticXMP; |
| |
| memcpy (mask.fMaskSubArea, |
| ifd .fMaskSubArea, |
| sizeof (ifd .fMaskSubArea)); |
| |
| mask.fMask.reset (image.Release ()); |
| |
| // If MaskSubArea is not valid, then zero all the fields. |
| |
| if (!mask.IsMaskSubAreaValid ()) |
| { |
| |
| memset (mask.fMaskSubArea, |
| 0, |
| sizeof (mask.fMaskSubArea)); |
| |
| } |
| |
| mask.fLossyCompressed.reset (lossyCompressed.Release ()); |
| |
| masks.push_back (mask); |
| |
| } |
| |
| fSemanticMasks = masks; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_negative::HasProfileGainTableMap () const |
| { |
| |
| return fProfileGainTableMap != nullptr; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_gain_table_map & dng_negative::ProfileGainTableMap () const |
| { |
| |
| DNG_REQUIRE (HasProfileGainTableMap (), "Missing profile gain table map"); |
| |
| return *fProfileGainTableMap; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetProfileGainTableMap |
| (const std::shared_ptr<const dng_gain_table_map> &gainTableMap) |
| { |
| |
| fProfileGainTableMap = gainTableMap; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::SetProfileGainTableMap (AutoPtr<dng_gain_table_map> &gainTableMap) |
| { |
| |
| fProfileGainTableMap.reset (gainTableMap.Release ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_negative::AdjustSemanticMasksForProxy (dng_host &host, |
| dng_image_writer &writer, |
| const dng_rect &originalStage3Bounds, |
| const dng_rect &defaultCropArea) |
| { |
| |
| if (!HasSemanticMask ()) |
| { |
| return; |
| } |
| |
| DNG_REQUIRE (fStage3Image.Get (), "Missing stage3 image"); |
| |
| const dng_rect newStage3Bounds = fStage3Image->Bounds (); |
| |
| // If original active area is different than the original default crop |
| // area, so this means during proxy conversion, the main image will be |
| // trimmed to the default crop area. This means we need to adjust the |
| // semantic masks, too. We need to resample and trim the masks so that |
| // they align properly with the trimmed main image. This is complicated by |
| // the fact that semantic masks may may be pre-cropped to exclude zero |
| // pixels (i.e., MaskSubArea support). |
| |
| const bool trimmedToDefaultCrop = (originalStage3Bounds != defaultCropArea); |
| |
| const uint32 maskCount = NumSemanticMasks (); |
| |
| for (uint32 i = 0; i < maskCount; i++) |
| { |
| |
| auto &mask = fSemanticMasks [i]; |
| |
| DNG_REQUIRE (mask.fMask, "Missing mask"); |
| |
| const bool needDownsampleMask = |
| (mask.fMask->Bounds ().W () > newStage3Bounds.W () || |
| mask.fMask->Bounds ().H () > newStage3Bounds.H ()) || |
| (mask.fMask->PixelType () != ttByte); |
| |
| if (needDownsampleMask || trimmedToDefaultCrop) |
| { |
| |
| AutoPtr<dng_image> image; |
| |
| // Can we just resample directly to proxy size? |
| |
| if (!mask.IsMaskSubAreaValid () & !trimmedToDefaultCrop) |
| { |
| |
| image.Reset (host.Make_dng_image (newStage3Bounds, |
| 1, |
| ttByte)); |
| |
| host.ResampleImage (*mask.fMask, |
| *image); |
| |
| } |
| |
| // Else we need to first create a full size trimmed mask. |
| |
| else |
| { |
| |
| // If we need to perform a second downsample step, then use the |
| // original mask pixel type as the intermediate pixel type. |
| |
| const bool needResizeToFinalArea = |
| (newStage3Bounds.Size () != defaultCropArea.Size ()); |
| |
| const uint32 fullResPixelType = |
| needResizeToFinalArea ? mask.fMask->PixelType () |
| : ttByte; |
| |
| AutoPtr<dng_image> fullResMask |
| (host.Make_dng_image (originalStage3Bounds, |
| 1, |
| fullResPixelType)); |
| |
| if (mask.IsMaskSubAreaValid ()) |
| { |
| |
| // MaskSubArea case. |
| |
| // Make a zero-filled image that represents the uncropped mask |
| // area (corresponding logically to the active area, or |
| // originalStage3Bounds). |
| |
| dng_point origin; |
| |
| dng_rect srcArea; |
| |
| mask.CalcMaskSubArea (origin, srcArea); |
| |
| const uint32 srcPixelType = mask.fMask->PixelType (); |
| |
| AutoPtr<dng_image> srcImage |
| (host.Make_dng_image (srcArea, |
| 1, |
| srcPixelType)); |
| |
| srcImage->SetZero (srcArea); |
| |
| // Copy the mask into the zero-filled image. |
| |
| AutoPtr<dng_image> subImage (mask.fMask->Clone ()); |
| |
| subImage->Offset (origin); |
| |
| srcImage->CopyArea (*subImage, |
| subImage->Bounds (), |
| 0, |
| 0, |
| 1); |
| |
| // Resample to active area. |
| |
| host.ResampleImage (*srcImage, |
| *fullResMask); |
| |
| } |
| |
| else |
| { |
| |
| // Without MaskSubArea case. Resample directly. |
| |
| host.ResampleImage (*mask.fMask, |
| *fullResMask); |
| |
| } |
| |
| // Trim to default crop area. |
| |
| fullResMask->Trim (defaultCropArea); |
| |
| image.Reset (fullResMask.Release ()); |
| |
| // Resize to new area, if needed. |
| |
| if (needResizeToFinalArea) |
| { |
| |
| AutoPtr<dng_image> temp |
| (host.Make_dng_image (newStage3Bounds, |
| 1, |
| ttByte)); |
| |
| host.ResampleImage (*image, |
| *temp); |
| |
| image.Reset (temp.Release ()); |
| |
| } |
| |
| } |
| |
| // Store. |
| |
| mask.fMask.reset (image.Release ()); |
| |
| // Clear MaskSubArea. |
| |
| memset (mask.fMaskSubArea, 0, sizeof (mask.fMaskSubArea)); |
| |
| // Lossy compressed data is no longer valid. |
| |
| mask.fLossyCompressed.reset (); |
| |
| } |
| |
| } |
| |
| CompressSemanticMasksJXL (host, |
| writer, |
| true); |
| |
| } |
| |
| /*****************************************************************************/ |