| /*****************************************************************************/ |
| // Copyright 2015-2023 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_big_table.h" |
| |
| #include "dng_1d_table.h" |
| #include "dng_bottlenecks.h" |
| #include "dng_abort_sniffer.h" |
| #include "dng_color_space.h" |
| #include "dng_globals.h" |
| #include "dng_host.h" |
| #include "dng_image.h" |
| #include "dng_image_writer.h" |
| #include "dng_info.h" |
| #include "dng_jxl.h" |
| #include "dng_memory_stream.h" |
| #include "dng_mutex.h" |
| #include "dng_negative.h" |
| #include "dng_stream.h" |
| |
| #if qDNGUseXMP |
| #include "dng_xmp.h" |
| #endif |
| |
| #include "zlib.h" |
| |
| #include <unordered_map> |
| |
| /*****************************************************************************/ |
| |
| class dng_big_table_cache |
| { |
| |
| private: |
| |
| dng_std_mutex fMutex; |
| |
| typedef std::pair <dng_fingerprint, |
| int32> RefCountsPair; |
| |
| typedef std::map <dng_fingerprint, |
| int32, |
| dng_fingerprint_less_than> RefCountsMap; |
| |
| RefCountsMap fRefCounts; |
| |
| std::vector<dng_fingerprint> fRecentlyUsed; |
| |
| protected: |
| |
| enum |
| { |
| kDefaultRecentlyUsedLimit = 5 |
| }; |
| |
| uint32 fRecentlyUsedLimit; |
| |
| protected: |
| |
| dng_big_table_cache (); |
| |
| void UseTable (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint); |
| |
| virtual void CacheIncrement (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint); |
| |
| virtual void CacheDecrement (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint); |
| |
| virtual void CacheAdd (dng_lock_std_mutex &lock, |
| const dng_big_table &table); |
| |
| virtual bool CacheExtract (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table); |
| |
| virtual void InsertTableData (dng_lock_std_mutex &lock, |
| const dng_big_table &table) = 0; |
| |
| virtual void EraseTableData (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint) = 0; |
| |
| virtual void ExtractTableData (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) = 0; |
| |
| virtual void Clear () |
| { |
| |
| fRefCounts.clear (); |
| |
| fRecentlyUsed.clear (); |
| |
| } |
| |
| public: |
| |
| virtual ~dng_big_table_cache (); |
| |
| void FlushRecentlyUsed (); |
| |
| static void Increment (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint); |
| |
| static void Decrement (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint); |
| |
| static void Add (dng_big_table_cache *cache, |
| const dng_big_table &table); |
| |
| static bool Extract (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table); |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| dng_big_table_cache::dng_big_table_cache () |
| |
| : fMutex () |
| |
| , fRefCounts () |
| |
| , fRecentlyUsed () |
| , fRecentlyUsedLimit (kDefaultRecentlyUsedLimit) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table_cache::~dng_big_table_cache () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::UseTable (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| // See if fingerprint is in recently used list. |
| |
| int32 lastIndex = (int32) fRecentlyUsed.size () - 1; |
| |
| for (int32 index = lastIndex; index >= 0; index--) |
| { |
| |
| if (fingerprint == fRecentlyUsed [index]) |
| { |
| |
| // Move to end of list if not already there. |
| |
| if (index != lastIndex) |
| { |
| |
| fRecentlyUsed.erase (fRecentlyUsed.begin () + index); |
| |
| fRecentlyUsed.push_back (fingerprint); |
| |
| } |
| |
| // Item is in recently used list, so we are done. |
| |
| return; |
| |
| } |
| |
| } |
| |
| // Is the recently used list full? |
| |
| if (fRecentlyUsed.size () == fRecentlyUsedLimit) |
| { |
| |
| CacheDecrement (lock, fRecentlyUsed.front ()); |
| |
| fRecentlyUsed.erase (fRecentlyUsed.begin ()); |
| |
| } |
| |
| // Add to end of list. |
| |
| fRecentlyUsed.push_back (fingerprint); |
| |
| CacheIncrement (lock, fingerprint); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::FlushRecentlyUsed () |
| { |
| |
| dng_lock_std_mutex lock (fMutex); |
| |
| while (!fRecentlyUsed.empty ()) |
| { |
| |
| CacheDecrement (lock, fRecentlyUsed.back ()); |
| |
| fRecentlyUsed.pop_back (); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::CacheIncrement (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| if (fingerprint.IsValid ()) |
| { |
| |
| RefCountsMap::iterator it = fRefCounts.find (fingerprint); |
| |
| if (it == fRefCounts.end ()) |
| { |
| |
| DNG_REPORT ("dng_big_table_cache::CacheIncrement" |
| "fingerprint not in cache"); |
| |
| } |
| |
| else |
| { |
| |
| it->second++; |
| |
| UseTable (lock, fingerprint); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::CacheDecrement (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| if (fingerprint.IsValid ()) |
| { |
| |
| RefCountsMap::iterator it = fRefCounts.find (fingerprint); |
| |
| if (it == fRefCounts.end ()) |
| { |
| |
| DNG_REPORT ("dng_big_table_cache::CacheDecrement" |
| "fingerprint not in cache"); |
| |
| } |
| |
| else if (--(it->second) == 0) |
| { |
| |
| fRefCounts.erase (it); |
| |
| EraseTableData (lock, fingerprint); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::CacheAdd (dng_lock_std_mutex &lock, |
| const dng_big_table &table) |
| { |
| |
| if (table.Fingerprint ().IsValid ()) |
| { |
| |
| RefCountsMap::iterator it = fRefCounts.find (table.Fingerprint ()); |
| |
| if (it == fRefCounts.end ()) |
| { |
| |
| fRefCounts.insert (RefCountsPair (table.Fingerprint (), 1)); |
| |
| InsertTableData (lock, table); |
| |
| } |
| |
| else |
| { |
| |
| it->second++; |
| |
| } |
| |
| UseTable (lock, table.Fingerprint ()); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_cache::CacheExtract (dng_lock_std_mutex &lock, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) |
| { |
| |
| if (fingerprint.IsValid ()) |
| { |
| |
| RefCountsMap::iterator it = fRefCounts.find (fingerprint); |
| |
| if (it != fRefCounts.end ()) |
| { |
| |
| it->second++; |
| |
| ExtractTableData (lock, fingerprint, table); |
| |
| UseTable (lock, fingerprint); |
| |
| return true; |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::Increment (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| if (cache) |
| { |
| |
| dng_lock_std_mutex lock (cache->fMutex); |
| |
| cache->CacheIncrement (lock, fingerprint); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::Decrement (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| if (cache) |
| { |
| |
| dng_lock_std_mutex lock (cache->fMutex); |
| |
| cache->CacheDecrement (lock, fingerprint); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache::Add (dng_big_table_cache *cache, |
| const dng_big_table &table) |
| { |
| |
| if (cache) |
| { |
| |
| dng_lock_std_mutex lock (cache->fMutex); |
| |
| cache->CacheAdd (lock, table); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_cache::Extract (dng_big_table_cache *cache, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) |
| { |
| |
| if (cache) |
| { |
| |
| dng_lock_std_mutex lock (cache->fMutex); |
| |
| return cache->CacheExtract (lock, fingerprint, table); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| class dng_look_table_cache : public dng_big_table_cache |
| { |
| |
| private: |
| |
| typedef std::pair <dng_fingerprint, |
| dng_look_table::table_data> TableDataPair; |
| |
| typedef std::map <dng_fingerprint, |
| dng_look_table::table_data, |
| dng_fingerprint_less_than> TableDataMap; |
| |
| TableDataMap fTableData; |
| |
| public: |
| |
| dng_look_table_cache () |
| |
| : fTableData () |
| |
| { |
| } |
| |
| virtual void Clear () |
| { |
| |
| dng_big_table_cache::Clear (); |
| |
| fTableData.clear (); |
| |
| } |
| |
| virtual void InsertTableData (dng_lock_std_mutex & /* lock */, |
| const dng_big_table &table) |
| { |
| |
| const dng_look_table *lookTable = static_cast |
| <const dng_look_table *> |
| (&table); |
| |
| fTableData.insert (TableDataPair (lookTable->Fingerprint (), |
| lookTable->fData)); |
| |
| } |
| |
| virtual void EraseTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| TableDataMap::iterator it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| fTableData.erase (it); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_look_table_cache::EraseTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| virtual void ExtractTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) |
| { |
| |
| TableDataMap::iterator it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| dng_look_table *lookTable = static_cast |
| <dng_look_table *> |
| (&table); |
| |
| lookTable->fData = it->second; |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_look_table_cache::ExtractTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| }; |
| |
| static dng_look_table_cache gLookTableCache; |
| |
| /*****************************************************************************/ |
| |
| class dng_rgb_table_cache : public dng_big_table_cache |
| { |
| |
| private: |
| |
| typedef std::pair <dng_fingerprint, |
| dng_rgb_table::table_data> TableDataPair; |
| |
| typedef std::map <dng_fingerprint, |
| dng_rgb_table::table_data, |
| dng_fingerprint_less_than> TableDataMap; |
| |
| TableDataMap fTableData; |
| |
| public: |
| |
| dng_rgb_table_cache () |
| |
| : fTableData () |
| |
| { |
| } |
| |
| virtual void Clear () |
| { |
| |
| dng_big_table_cache::Clear (); |
| |
| fTableData.clear (); |
| |
| } |
| |
| virtual void InsertTableData (dng_lock_std_mutex & /* lock */, |
| const dng_big_table &table) |
| { |
| |
| const dng_rgb_table *rgbTable = static_cast |
| <const dng_rgb_table *> |
| (&table); |
| |
| fTableData.insert (TableDataPair (rgbTable->Fingerprint (), |
| rgbTable->fData)); |
| |
| } |
| |
| virtual void EraseTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint) |
| { |
| |
| TableDataMap::iterator it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| fTableData.erase (it); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_rgb_table_cache::EraseTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| virtual void ExtractTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) |
| { |
| |
| TableDataMap::iterator it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| dng_rgb_table *rgbTable = static_cast |
| <dng_rgb_table *> |
| (&table); |
| |
| rgbTable->fData = it->second; |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_rgb_table_cache::ExtractTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| }; |
| |
| static dng_rgb_table_cache gRGBTableCache; |
| |
| /*****************************************************************************/ |
| |
| class dng_image_table_cache : public dng_big_table_cache |
| { |
| |
| private: |
| |
| std::unordered_map <dng_fingerprint, |
| dng_image_table_data, |
| dng_fingerprint_hash> fTableData; |
| |
| public: |
| |
| void Clear () override |
| { |
| |
| dng_big_table_cache::Clear (); |
| |
| fTableData.clear (); |
| |
| } |
| |
| void InsertTableData (dng_lock_std_mutex & /* lock */, |
| const dng_big_table &table) override |
| { |
| |
| const dng_image_table *imageTable = static_cast |
| <const dng_image_table *> |
| (&table); |
| |
| dng_image_table_data data; |
| |
| imageTable->GetData (data); |
| |
| fTableData.insert (std::make_pair (imageTable->Fingerprint (), |
| data)); |
| |
| } |
| |
| void EraseTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint) override |
| { |
| |
| auto it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| fTableData.erase (it); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_image_table_cache::EraseTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| void ExtractTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) override |
| { |
| |
| auto it = fTableData.find (fingerprint); |
| |
| if (it != fTableData.end ()) |
| { |
| |
| dng_image_table *imageTable = static_cast |
| <dng_image_table *> |
| (&table); |
| |
| imageTable->SetData (it->second); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_image_table_cache::ExtractTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| }; |
| |
| static dng_image_table_cache gImageTableCache; |
| |
| /*****************************************************************************/ |
| |
| class dng_packed_image_table_cache : public dng_big_table_cache |
| { |
| |
| private: |
| |
| struct entry_t |
| { |
| |
| public: |
| |
| dng_image_table_data fImageData; |
| |
| std::shared_ptr<const dng_memory_block> fBlock; |
| |
| // Image properties. |
| |
| dng_point fSize; |
| |
| uint32 fPlanes = 0; |
| |
| uint32 fPixelType = 0; |
| |
| }; |
| |
| std::unordered_map <dng_fingerprint, |
| entry_t, |
| dng_fingerprint_hash> fEntries; |
| |
| public: |
| |
| void Clear () override |
| { |
| |
| dng_big_table_cache::Clear (); |
| |
| fEntries.clear (); |
| |
| } |
| |
| void InsertTableData (dng_lock_std_mutex & /* lock */, |
| const dng_big_table &table) override |
| { |
| |
| // printf ("dng_packed_image_table_cache::InsertTableData\n"); |
| |
| const auto &src = static_cast <const dng_packed_image_table &> (table); |
| |
| entry_t entry; |
| |
| entry.fBlock = src.ShareBlock (); |
| entry.fSize = src.fSize; |
| entry.fPlanes = src.fPlanes; |
| entry.fPixelType = src.fPixelType; |
| |
| if (src.IsValidUnpacked ()) |
| src.Table ().GetData (entry.fImageData); |
| |
| fEntries.insert (std::make_pair (src.Fingerprint (), entry)); |
| |
| } |
| |
| void EraseTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint) override |
| { |
| |
| // printf ("dng_packed_image_table_cache::EraseTableData\n"); |
| |
| auto iter = fEntries.find (fingerprint); |
| |
| if (iter != fEntries.end ()) |
| { |
| |
| fEntries.erase (iter); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_packed_image_table_cache::EraseTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| void ExtractTableData (dng_lock_std_mutex & /* lock */, |
| const dng_fingerprint &fingerprint, |
| dng_big_table &table) override |
| { |
| |
| // printf ("dng_packed_image_table_cache::ExtractTableData\n"); |
| |
| auto iter = fEntries.find (fingerprint); |
| |
| if (iter != fEntries.end ()) |
| { |
| |
| auto &dst = static_cast <dng_packed_image_table &> (table); |
| |
| dst.fTableDigest = fingerprint; |
| |
| dst.fBlock = iter->second.fBlock; |
| dst.fSize = iter->second.fSize; |
| dst.fPlanes = iter->second.fPlanes; |
| dst.fPixelType = iter->second.fPixelType; |
| |
| AutoPtr<dng_image_table> temp (dst.MakeTable ()); |
| |
| temp->SetData (iter->second.fImageData); |
| |
| dst.fTable.reset (temp.Release ()); |
| |
| } |
| |
| else |
| { |
| |
| DNG_REPORT ("dng_packed_image_table_cache::ExtractTableData" |
| "fingerprint not in cache"); |
| |
| } |
| |
| } |
| |
| }; |
| |
| static dng_packed_image_table_cache gPackedImageTableCache; |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache_flush () |
| { |
| |
| gLookTableCache.FlushRecentlyUsed (); |
| |
| gRGBTableCache.FlushRecentlyUsed (); |
| |
| gImageTableCache.FlushRecentlyUsed (); |
| |
| gPackedImageTableCache.FlushRecentlyUsed (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_cache_clear () |
| { |
| |
| gLookTableCache.Clear (); |
| |
| gRGBTableCache.Clear (); |
| |
| gImageTableCache.Clear (); |
| |
| gPackedImageTableCache.Clear (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table::dng_big_table (dng_big_table_cache *cache) |
| |
| : fFingerprint () |
| , fCache (cache) |
| , fIsMissing (false) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table::dng_big_table (const dng_big_table &table) |
| |
| : fFingerprint (table.fFingerprint) |
| , fCache (table.fCache ) |
| , fIsMissing (false ) |
| |
| { |
| |
| dng_big_table_cache::Increment (fCache, fFingerprint); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table & dng_big_table::operator= (const dng_big_table &table) |
| { |
| |
| if (fFingerprint != table.fFingerprint || |
| fCache != table.fCache ) |
| { |
| |
| dng_big_table_cache::Decrement (fCache, fFingerprint); |
| |
| fFingerprint = table.fFingerprint; |
| fCache = table.fCache; |
| |
| dng_big_table_cache::Increment (fCache, fFingerprint); |
| |
| } |
| |
| return *this; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table::~dng_big_table () |
| { |
| |
| dng_big_table_cache::Decrement (fCache, fFingerprint); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_fingerprint & dng_big_table::Fingerprint () const |
| { |
| |
| return fFingerprint; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_big_table::ComputeFingerprint () const |
| { |
| |
| dng_md5_printer_le_stream stream; |
| |
| PutStream (stream, true); |
| |
| return stream.Result (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table::RecomputeFingerprint () |
| { |
| |
| dng_big_table_cache::Decrement (fCache, fFingerprint); |
| |
| fFingerprint.Clear (); |
| |
| if (IsValid ()) |
| { |
| |
| fFingerprint = ComputeFingerprint (); |
| |
| // Try extract first to force sharing of table data memory if |
| // table data is already in cache. |
| |
| if (!dng_big_table_cache::Extract (fCache, fFingerprint, *this)) |
| { |
| |
| // Otherwise add to cache. |
| |
| dng_big_table_cache::Add (fCache, *this); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table::DecodeFromBinary (dng_host &host, |
| const uint8 * compressedData, |
| uint32 compressedSize, |
| AutoPtr<dng_memory_block> *uncompressedCache) |
| { |
| |
| // Decompress the data, if required. |
| |
| if (UseCompression ()) |
| { |
| |
| if (compressedSize < 5) |
| { |
| return false; |
| } |
| |
| uint8 *uncompressedData; |
| uint32 uncompressedSize; |
| |
| AutoPtr<dng_memory_block> uncompressedBlock; |
| |
| if (uncompressedCache && uncompressedCache->Get ()) |
| { |
| uncompressedData = uncompressedCache->Get ()->Buffer_uint8 (); |
| uncompressedSize = uncompressedCache->Get ()->LogicalSize (); |
| } |
| |
| else |
| { |
| |
| // Uncompressed size is stored in first four bytes of decoded data, |
| // little endian order. |
| |
| uncompressedSize = (((uint32) compressedData [0]) ) + |
| (((uint32) compressedData [1]) << 8) + |
| (((uint32) compressedData [2]) << 16) + |
| (((uint32) compressedData [3]) << 24); |
| |
| uncompressedBlock.Reset (host.Allocate (uncompressedSize)); |
| |
| uncompressedData = uncompressedBlock->Buffer_uint8 (); |
| |
| uLongf destLen = uncompressedSize; |
| |
| int zResult = ::uncompress (uncompressedBlock->Buffer_uint8 (), |
| &destLen, |
| compressedData + 4, |
| compressedSize - 4); |
| |
| if (zResult != Z_OK) |
| { |
| return false; |
| } |
| |
| if (uncompressedCache) |
| { |
| uncompressedCache->Reset (uncompressedBlock.Release ()); |
| } |
| |
| } |
| |
| // Now read in the table data from the uncompressed stream. |
| |
| try |
| { |
| |
| dng_stream stream (uncompressedData, |
| uncompressedSize); |
| |
| stream.SetLittleEndian (); |
| |
| stream.SetSniffer (host.Sniffer ()); |
| |
| if (!GetStream (stream)) |
| { |
| return false; |
| } |
| |
| } |
| |
| catch (dng_exception &except) |
| { |
| |
| if (host.IsTransientError (except.ErrorCode ())) |
| { |
| throw; |
| } |
| |
| return false; |
| |
| } |
| |
| catch (...) |
| { |
| |
| return false; |
| |
| } |
| |
| } |
| |
| // Simple uncompressed stream for this big table class. |
| |
| else |
| { |
| |
| try |
| { |
| |
| dng_stream stream (compressedData, |
| compressedSize); |
| |
| stream.SetLittleEndian (); |
| |
| stream.SetSniffer (host.Sniffer ()); |
| |
| if (!GetStream (stream)) |
| { |
| return false; |
| } |
| |
| } |
| |
| catch (dng_exception &except) |
| { |
| |
| if (host.IsTransientError (except.ErrorCode ())) |
| { |
| throw; |
| } |
| |
| return false; |
| |
| } |
| |
| catch (...) |
| { |
| |
| return false; |
| |
| } |
| |
| } |
| |
| // Force recomputation of fingerprint. |
| |
| RecomputeFingerprint (); |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table::ASCIItoBinary (dng_memory_allocator &allocator, |
| const char *sPtr, |
| uint32 sCount, |
| AutoPtr<dng_memory_block> &dBlock, |
| uint32 &dCount) |
| { |
| |
| // This binary to text encoding is very similar to the Z85 |
| // encoding, but the exact character set has been adjusted to |
| // encode more cleanly into XMP. |
| |
| static uint8 kDecodeTable [96] = |
| { |
| |
| 0xFF, // space |
| 0x44, // ! |
| 0xFF, // " |
| 0x54, // # |
| 0x53, // $ |
| 0x52, // % |
| 0xFF, // & |
| 0x49, // ' |
| 0x4B, // ( |
| 0x4C, // ) |
| 0x46, // * |
| 0x41, // + |
| 0xFF, // , |
| 0x3F, // - |
| 0x3E, // . |
| 0x45, // / |
| |
| 0x00, 0x01, 0x02, 0x03, 0x04, // 01234 |
| 0x05, 0x06, 0x07, 0x08, 0x09, // 56789 |
| |
| 0x40, // : |
| 0xFF, // ; |
| 0xFF, // < |
| 0x42, // = |
| 0xFF, // > |
| 0x47, // ? |
| 0x51, // @ |
| |
| 0x24, 0x25, 0x26, 0x27, 0x28, // ABCDE |
| 0x29, 0x2A, 0x2B, 0x2C, 0x2D, // FGHIJ |
| 0x2E, 0x2F, 0x30, 0x31, 0x32, // KLMNO |
| 0x33, 0x34, 0x35, 0x36, 0x37, // PQRST |
| 0x38, 0x39, 0x3A, 0x3B, 0x3C, // UVWXY |
| 0x3D, // Z |
| |
| 0x4D, // [ |
| 0xFF, // backslash |
| 0x4E, // ] |
| 0x43, // ^ |
| 0xFF, // _ |
| 0x48, // ` |
| |
| 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // abcde |
| 0x0F, 0x10, 0x11, 0x12, 0x13, // fghij |
| 0x14, 0x15, 0x16, 0x17, 0x18, // klmno |
| 0x19, 0x1A, 0x1B, 0x1C, 0x1D, // pqrst |
| 0x1E, 0x1F, 0x20, 0x21, 0x22, // uvwxy |
| 0x23, // z |
| |
| 0x4F, // { |
| 0x4A, // | |
| 0x50, // } |
| 0xFF, // ~ |
| 0xFF // del |
| |
| }; |
| |
| dCount = 0; |
| |
| uint32 maxDecodedSize = (sCount + 4) / 5 * 4; |
| |
| dBlock.Reset (allocator.Allocate (maxDecodedSize)); |
| |
| uint32 phase = 0; |
| uint32 value; |
| |
| uint8 *dPtr = dBlock->Buffer_uint8 (); |
| |
| for (uint32 j = 0; j < sCount; j++) |
| { |
| |
| uint8 e = (uint8) sPtr [j]; |
| |
| if (e < 32 || e > 127) |
| { |
| continue; |
| } |
| |
| uint32 d = kDecodeTable [e - 32]; |
| |
| if (d > 85) |
| { |
| continue; |
| } |
| |
| phase++; |
| |
| if (phase == 1) |
| { |
| value = d; |
| } |
| |
| else if (phase == 2) |
| { |
| value += d * 85; |
| } |
| |
| else if (phase == 3) |
| { |
| value += d * (85 * 85); |
| } |
| |
| else if (phase == 4) |
| { |
| value += d * (85 * 85 * 85); |
| } |
| |
| else |
| { |
| |
| value += d * (85 * 85 * 85 * 85); |
| |
| dPtr [0] = (uint8) (value ); |
| dPtr [1] = (uint8) (value >> 8); |
| dPtr [2] = (uint8) (value >> 16); |
| dPtr [3] = (uint8) (value >> 24); |
| |
| dPtr += 4; |
| |
| dCount += 4; |
| |
| phase = 0; |
| |
| } |
| |
| } |
| |
| if (phase > 3) |
| { |
| |
| dPtr [2] = (uint8) (value >> 16); |
| |
| dCount++; |
| |
| } |
| |
| if (phase > 2) |
| { |
| |
| dPtr [1] = (uint8) (value >> 8); |
| |
| dCount++; |
| |
| } |
| |
| if (phase > 1) |
| { |
| |
| dPtr [0] = (uint8) (value); |
| |
| dCount++; |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table::DecodeFromString (dng_host &host, |
| const dng_string &block1) |
| { |
| |
| // Decode the text to binary. |
| |
| AutoPtr<dng_memory_block> block2; |
| |
| uint32 compressedSize = 0; |
| |
| ASCIItoBinary (host.Allocator (), |
| block1.Get (), |
| block1.Length (), |
| block2, |
| compressedSize); |
| |
| // Then decode table from the binary data. |
| |
| return DecodeFromBinary (host, |
| block2->Buffer_uint8 (), |
| compressedSize); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_memory_block* dng_big_table::EncodeAsBinary (dng_memory_allocator &allocator, |
| uint32 &compressedSize) const |
| { |
| |
| // Stream to a binary block. |
| |
| AutoPtr<dng_memory_block> block1; |
| |
| { |
| |
| dng_memory_stream stream (allocator); |
| |
| stream.SetLittleEndian (); |
| |
| PutStream (stream, false); |
| |
| block1.Reset (stream.AsMemoryBlock (allocator)); |
| |
| } |
| |
| // If we are not compressing this type, we are done. |
| |
| if (!UseCompression ()) |
| { |
| |
| compressedSize = block1->LogicalSize (); |
| |
| return block1.Release (); |
| |
| } |
| |
| // Compress the block. |
| |
| AutoPtr<dng_memory_block> block2; |
| |
| { |
| |
| uint32 uncompressedSize = block1->LogicalSize (); |
| |
| uint32 safeCompressedSize = uncompressedSize + (uncompressedSize >> 8) + 64; |
| |
| block2.Reset (allocator.Allocate (safeCompressedSize + 4)); |
| |
| // Store uncompressed size in first four bytes of compressed block. |
| |
| uint8 *dPtr = block2->Buffer_uint8 (); |
| |
| dPtr [0] = (uint8) (uncompressedSize ); |
| dPtr [1] = (uint8) (uncompressedSize >> 8); |
| dPtr [2] = (uint8) (uncompressedSize >> 16); |
| dPtr [3] = (uint8) (uncompressedSize >> 24); |
| |
| uLongf dCount = safeCompressedSize; |
| |
| int zResult = ::compress2 (dPtr + 4, |
| &dCount, |
| block1->Buffer_uint8 (), |
| uncompressedSize, |
| Z_DEFAULT_COMPRESSION); |
| |
| if (zResult != Z_OK) |
| { |
| ThrowMemoryFull (); |
| } |
| |
| compressedSize = (uint32) dCount + 4; |
| |
| block1.Reset (); |
| |
| } |
| |
| return block2.Release (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_memory_block* dng_big_table::EncodeAsString (dng_memory_allocator &allocator) const |
| { |
| |
| // Get compressed binary data. |
| |
| uint32 compressedSize; |
| |
| AutoPtr<dng_memory_block> block2 (EncodeAsBinary (allocator, compressedSize)); |
| |
| // Encode binary to text. |
| |
| AutoPtr<dng_memory_block> block3; |
| |
| { |
| |
| // This binary to text encoding is very similar to the Z85 |
| // encoding, but the exact character set has been adjusted to |
| // encode more cleanly into XMP. |
| |
| static const char *kEncodeTable = |
| "0123456789" |
| "abcdefghij" |
| "klmnopqrst" |
| "uvwxyzABCD" |
| "EFGHIJKLMN" |
| "OPQRSTUVWX" |
| "YZ.-:+=^!/" |
| "*?`'|()[]{" |
| "}@%$#"; |
| |
| uint32 safeEncodedSize = compressedSize + |
| (compressedSize >> 2) + |
| (compressedSize >> 6) + |
| 16; |
| |
| block3.Reset (allocator.Allocate (safeEncodedSize)); |
| |
| uint8 *sPtr = block2->Buffer_uint8 (); |
| |
| sPtr [compressedSize ] = 0; |
| sPtr [compressedSize + 1] = 0; |
| sPtr [compressedSize + 2] = 0; |
| |
| uint8 *dPtr = block3->Buffer_uint8 (); |
| |
| while (compressedSize) |
| { |
| |
| uint32 x0 = (((uint32) sPtr [0]) ) + |
| (((uint32) sPtr [1]) << 8) + |
| (((uint32) sPtr [2]) << 16) + |
| (((uint32) sPtr [3]) << 24); |
| |
| sPtr += 4; |
| |
| uint32 x1 = x0 / 85; |
| |
| *dPtr++ = kEncodeTable [x0 - x1 * 85]; |
| |
| uint32 x2 = x1 / 85; |
| |
| *dPtr++ = kEncodeTable [x1 - x2 * 85]; |
| |
| if (!--compressedSize) |
| break; |
| |
| uint32 x3 = x2 / 85; |
| |
| *dPtr++ = kEncodeTable [x2 - x3 * 85]; |
| |
| if (!--compressedSize) |
| break; |
| |
| uint32 x4 = x3 / 85; |
| |
| *dPtr++ = kEncodeTable [x3 - x4 * 85]; |
| |
| if (!--compressedSize) |
| break; |
| |
| *dPtr++ = kEncodeTable [x4]; |
| |
| compressedSize--; |
| |
| } |
| |
| *dPtr = 0; |
| |
| block2.Reset (); |
| |
| } |
| |
| return block3.Release (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table::ExtractFromCache (const dng_fingerprint &fingerprint) |
| { |
| |
| if (dng_big_table_cache::Extract (fCache, fingerprint, *this)) |
| { |
| |
| fFingerprint = fingerprint; |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| #if qDNGUseXMP |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table::ReadTableFromXMP (const dng_xmp &xmp, |
| const char *ns, |
| const dng_fingerprint &fingerprint, |
| dng_big_table_storage *storage, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| // See if we can skip reading the table data, and just grab from cache. |
| |
| if (ExtractFromCache (fingerprint)) |
| { |
| |
| return true; |
| |
| } |
| |
| // Next see if we can get the table from the storage object. |
| |
| if (storage && storage->ReadTable (*this, fingerprint, xmp.Allocator ())) |
| { |
| |
| return true; |
| |
| } |
| |
| // Not in cache nor storage, so we need to read from XMP. |
| |
| dng_host host (&xmp.Allocator (), sniffer); |
| |
| host.SniffForAbort (); |
| |
| dng_string tablePath; |
| |
| tablePath.Set ("Table_"); |
| |
| tablePath.Append (dng_xmp::EncodeFingerprint (fingerprint).Get ()); |
| |
| dng_string block1; |
| |
| if (!xmp.GetString (ns, |
| tablePath.Get (), |
| block1)) |
| { |
| |
| DNG_REPORT ("Missing big table data"); |
| |
| return false; |
| |
| } |
| |
| host.SniffForAbort (); |
| |
| bool ok = DecodeFromString (host, block1); |
| |
| block1.Clear (); |
| |
| host.SniffForAbort (); |
| |
| // Validate fingerprint match. Only bother doing this if decoding |
| // succeeded, otherwise we expect the fingerprint to be wrong and the |
| // assert message is not helpful. |
| |
| if (ok) |
| { |
| |
| DNG_ASSERT (Fingerprint () == fingerprint, |
| "dng_big_table fingerprint mismatch"); |
| |
| } |
| |
| // It worked! |
| |
| return ok; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table::ReadFromXMP (const dng_xmp &xmp, |
| const char *ns, |
| const char *path, |
| dng_big_table_storage &storage, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| dng_fingerprint fingerprint; |
| |
| if (!xmp.GetFingerprint (ns, path, fingerprint)) |
| { |
| |
| return false; |
| |
| } |
| |
| // See if we can skip reading the table data, and just grab from cache. |
| |
| if (ExtractFromCache (fingerprint)) |
| { |
| |
| return true; |
| |
| } |
| |
| // Next see if we can get the table from the storage object. |
| |
| if (storage.ReadTable (*this, fingerprint, xmp.Allocator ())) |
| { |
| |
| return true; |
| |
| } |
| |
| // Read in the table data. We already checked the storage object |
| // (above), so pass in nullptr for 4th argument. |
| |
| if (ReadTableFromXMP (xmp, ns, fingerprint, nullptr, sniffer)) |
| { |
| |
| return true; |
| |
| } |
| |
| // Unable to find table data anywhere. Notify storage object. |
| |
| storage.MissingTable (fingerprint); |
| |
| // Also make a note that this table is missing. |
| |
| SetMissing (); |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table::WriteToXMP (dng_xmp &xmp, |
| const char *ns, |
| const char *path, |
| dng_big_table_storage &storage) const |
| { |
| |
| const dng_fingerprint &fingerprint = Fingerprint (); |
| |
| if (!fingerprint.IsValid () || IsMissing ()) |
| { |
| |
| xmp.Remove (ns, path); |
| |
| return; |
| |
| } |
| |
| xmp.SetFingerprint (ns, path, fingerprint); |
| |
| // See if we can just use the storage object to store the table. |
| |
| if (storage.WriteTable (*this, fingerprint, xmp.Allocator ())) |
| { |
| |
| return; |
| |
| } |
| |
| dng_string tablePath; |
| |
| tablePath.Set ("Table_"); |
| |
| tablePath.Append (dng_xmp::EncodeFingerprint (fingerprint).Get ()); |
| |
| if (xmp.Exists (ns, tablePath.Get ())) |
| { |
| |
| return; |
| |
| } |
| |
| AutoPtr<dng_memory_block> block; |
| |
| block.Reset (EncodeAsString (xmp.Allocator ())); |
| |
| xmp.Set (ns, |
| tablePath.Get (), |
| block->Buffer_char ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| #endif // qDNGUseXMP |
| |
| /*****************************************************************************/ |
| |
| #if qDNGValidate |
| |
| void dng_big_table::WriteUncompressedStream (dng_stream &stream) const |
| { |
| |
| stream.SetLittleEndian (); |
| |
| PutStream (stream, false); |
| |
| } |
| |
| #endif // qDNGValidate |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_dictionary::HasTable (const dng_fingerprint &fingerprint) const |
| { |
| |
| if (fMap.find (fingerprint) != fMap.end ()) |
| { |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_dictionary::GetTable (const dng_fingerprint &fingerprint, |
| dng_ref_counted_block &block) const |
| { |
| |
| const auto it = fMap.find (fingerprint); |
| |
| if (it != fMap.end ()) |
| { |
| |
| block = it->second; |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_dictionary::AddTable (const dng_fingerprint &fingerprint, |
| const dng_ref_counted_block &block) |
| { |
| |
| if (fMap.find (fingerprint) == fMap.end ()) |
| { |
| |
| fMap.insert (std::pair<dng_fingerprint, |
| dng_ref_counted_block> (fingerprint, |
| block)); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_dictionary::CopyToDictionary |
| (dng_big_table_dictionary &dictionary) const |
| |
| { |
| |
| for (auto it = fMap.cbegin (); it != fMap.cend (); ++it) |
| { |
| |
| dictionary.AddTable (it->first, |
| it->second); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table_index::dng_big_table_index () |
| |
| : fMap () |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_index::HasEntry (const dng_fingerprint &fingerprint) const |
| { |
| |
| return fMap.find (fingerprint) != fMap.end (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_index::GetEntry (const dng_fingerprint &fingerprint, |
| uint32 &tableSize, |
| uint64 &tableOffset) const |
| { |
| |
| auto it = fMap.find (fingerprint); |
| |
| if (it != fMap.end ()) |
| { |
| |
| tableSize = it->second.fTableSize; |
| tableOffset = it->second.fTableOffset; |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_index::AddEntry (const dng_fingerprint &fingerprint, |
| uint32 tableSize, |
| uint64 tableOffset) |
| { |
| |
| if (fMap.find (fingerprint) == fMap.end ()) |
| { |
| |
| struct IndexEntry entry; |
| |
| entry.fTableSize = tableSize; |
| entry.fTableOffset = tableOffset; |
| |
| fMap.insert (std::pair<dng_fingerprint, |
| IndexEntry> (fingerprint, entry)); |
| |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| bool dng_big_table_group_index::GetEntry (const dng_fingerprint &groupDigest, |
| dng_fingerprint &instanceDigest) const |
| { |
| |
| auto it = fMap.find (groupDigest); |
| |
| if (it != fMap.end ()) |
| { |
| |
| instanceDigest = it->second; |
| |
| return true; |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_big_table_storage::dng_big_table_storage () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_big_table_storage::~dng_big_table_storage () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_storage::ReadTable (dng_big_table & /* table */, |
| const dng_fingerprint & /* fingerprint */, |
| dng_memory_allocator & /* allocator */) |
| { |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_storage::WriteTable (const dng_big_table &table, |
| const dng_fingerprint & /* fingerprint */, |
| dng_memory_allocator & /* allocator */) |
| { |
| |
| if (table.IsEmbedNever ()) |
| { |
| return true; |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_big_table_storage::MissingTable (const dng_fingerprint & /* fingerprint */) |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_big_table_storage::GroupToInstance (const dng_fingerprint & /* groupDigest */, |
| dng_fingerprint & /* instanceDigest */) const |
| { |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_look_table::dng_look_table () |
| |
| : dng_big_table (&gLookTableCache) |
| |
| , fData () |
| , fAmount (1.0) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_look_table::dng_look_table (const dng_look_table &table) |
| |
| : dng_big_table (table) |
| |
| , fData (table.fData) |
| , fAmount (table.fAmount) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_look_table & dng_look_table::operator= (const dng_look_table &table) |
| { |
| |
| dng_big_table::operator= (table); |
| |
| fData = table.fData; |
| fAmount = table.fAmount; |
| |
| return *this; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_look_table::~dng_look_table () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_look_table::Set (const dng_hue_sat_map &map, |
| uint32 encoding) |
| { |
| |
| fData.fMap = map; |
| fData.fEncoding = encoding; |
| |
| fData.ComputeMonochrome (); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_look_table::IsValid () const |
| { |
| |
| if (IsMissing ()) |
| { |
| return false; |
| } |
| |
| return fData.fMap.IsValid (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_look_table::SetInvalid () |
| { |
| |
| *this = dng_look_table (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_look_table::GetStream (dng_stream &stream) |
| { |
| |
| table_data data; |
| |
| if (stream.Get_uint32 () != btt_LookTable) |
| { |
| return false; |
| } |
| |
| uint32 version = stream.Get_uint32 (); |
| |
| if (version != kLookTableVersion1 && |
| version != kLookTableVersion2) |
| { |
| ThrowBadFormat ("Unknown look table version"); |
| } |
| |
| uint32 hueDivisions = stream.Get_uint32 (); |
| uint32 satDivisions = stream.Get_uint32 (); |
| uint32 valDivisions = stream.Get_uint32 (); |
| |
| if (hueDivisions < 1 || hueDivisions > kMaxHueSamples || |
| satDivisions < 1 || satDivisions > kMaxSatSamples || |
| valDivisions < 1 || valDivisions > kMaxValSamples || |
| (dng_safe_uint32 (hueDivisions) * |
| dng_safe_uint32 (satDivisions) * |
| dng_safe_uint32 (valDivisions)).Get () > kMaxTotalSamples) |
| { |
| ThrowBadFormat (); |
| } |
| |
| data.fMap.SetDivisions (hueDivisions, |
| satDivisions, |
| valDivisions); |
| |
| uint32 count = data.fMap.DeltasCount (); |
| |
| dng_hue_sat_map::HSBModify * deltas = data.fMap.GetDeltas (); |
| |
| for (uint32 index = 0; index < count; index++) |
| { |
| |
| deltas->fHueShift = stream.Get_real32 (); |
| deltas->fSatScale = stream.Get_real32 (); |
| deltas->fValScale = stream.Get_real32 (); |
| |
| deltas++; |
| |
| } |
| |
| data.fMap.AssignNewUniqueRuntimeFingerprint (); |
| |
| data.fEncoding = stream.Get_uint32 (); |
| |
| if (data.fEncoding != encoding_Linear && |
| data.fEncoding != encoding_sRGB) |
| { |
| ThrowBadFormat ("Unknown look table encoding"); |
| } |
| |
| if (version != kLookTableVersion1) |
| { |
| |
| data.fMinAmount = stream.Get_real64 (); |
| data.fMaxAmount = stream.Get_real64 (); |
| |
| if (data.fMinAmount < 0.0 || data.fMinAmount > 1.0 || data.fMaxAmount < 1.0) |
| { |
| ThrowBadFormat ("Invalid min/max amount for look table"); |
| } |
| |
| } |
| |
| else |
| { |
| |
| data.fMinAmount = 1.0; |
| data.fMaxAmount = 1.0; |
| |
| } |
| |
| data.ComputeMonochrome (); |
| |
| if (stream.Position () + 4 <= stream.Length ()) |
| { |
| data.fFlags = stream.Get_uint32 (); |
| } |
| |
| fData = data; |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_look_table::PutStream (dng_stream &stream, |
| bool /* forFingerprint */) const |
| { |
| |
| DNG_REQUIRE (IsValid (), "Invalid Look Table"); |
| |
| stream.Put_uint32 (btt_LookTable); |
| |
| uint32 version = kLookTableVersion1; |
| |
| if (fData.fMinAmount != 1.0 || |
| fData.fMaxAmount != 1.0) |
| { |
| version = kLookTableVersion2; |
| } |
| |
| stream.Put_uint32 (version); |
| |
| uint32 hueDivisions; |
| uint32 satDivisions; |
| uint32 valDivisions; |
| |
| fData.fMap.GetDivisions (hueDivisions, |
| satDivisions, |
| valDivisions); |
| |
| stream.Put_uint32 (hueDivisions); |
| stream.Put_uint32 (satDivisions); |
| stream.Put_uint32 (valDivisions); |
| |
| uint32 count = fData.fMap.DeltasCount (); |
| |
| const dng_hue_sat_map::HSBModify * deltas = fData.fMap.GetConstDeltas (); |
| |
| for (uint32 index = 0; index < count; index++) |
| { |
| |
| stream.Put_real32 (deltas->fHueShift); |
| stream.Put_real32 (deltas->fSatScale); |
| stream.Put_real32 (deltas->fValScale); |
| |
| deltas++; |
| |
| } |
| |
| stream.Put_uint32 (fData.fEncoding); |
| |
| if (version != kLookTableVersion1) |
| { |
| |
| stream.Put_real64 (fData.fMinAmount); |
| stream.Put_real64 (fData.fMaxAmount); |
| |
| } |
| |
| if (fData.fFlags != 0) |
| { |
| |
| stream.Put_uint32 (fData.fFlags); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_rgb_table::dng_rgb_table () |
| |
| : dng_big_table (&gRGBTableCache) |
| |
| , fData () |
| , fAmount (1.0) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_rgb_table::dng_rgb_table (const dng_rgb_table &table) |
| |
| : dng_big_table (table) |
| |
| , fData (table.fData) |
| , fAmount (table.fAmount) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_rgb_table & dng_rgb_table::operator= (const dng_rgb_table &table) |
| { |
| |
| dng_big_table::operator= (table); |
| |
| fData = table.fData; |
| fAmount = table.fAmount; |
| |
| return *this; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_rgb_table::~dng_rgb_table () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_rgb_table::IsValid () const |
| { |
| |
| if (IsMissing ()) |
| { |
| return false; |
| } |
| |
| // If table itself is invalid, then invalid. |
| |
| if (fData.fDimensions == 0) |
| return false; |
| |
| // If table has some effect, then valid. |
| |
| if (fAmount > 0.0) |
| return true; |
| |
| // Does the matrix itself do any clipping? |
| |
| if (fData.fPrimaries == primaries_ProPhoto || |
| fData.fGamut == gamut_extend ) |
| return false; |
| |
| // Table is a NOP but there is some gamut clipping. |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_rgb_table::SetInvalid () |
| { |
| |
| *this = dng_rgb_table (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_rgb_table::Set (uint32 dimensions, |
| uint32 divisions, |
| dng_ref_counted_block samples) |
| { |
| |
| if (dimensions == 1) |
| { |
| |
| if (divisions < kMinDivisions1D || |
| divisions > kMaxDivisions1D) |
| { |
| |
| ThrowProgramError ("Bad 1D divisions"); |
| |
| } |
| |
| if (samples.LogicalSize () != divisions * 4 * sizeof (uint16)) |
| { |
| |
| ThrowProgramError ("Bad 1D sample count"); |
| |
| } |
| |
| } |
| |
| else if (dimensions == 3) |
| { |
| |
| if (divisions < kMinDivisions3D || |
| divisions > kMaxDivisions3D_InMemory) |
| { |
| |
| ThrowProgramError ("Bad 3D divisions"); |
| |
| } |
| |
| if (samples.LogicalSize () != divisions * |
| divisions * |
| divisions * 4 * sizeof (uint16)) |
| { |
| |
| ThrowProgramError ("Bad 3D sample count"); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| ThrowProgramError ("Bad dimensions"); |
| |
| } |
| |
| fData.fDimensions = dimensions; |
| |
| fData.fDivisions = divisions; |
| |
| fData.fSamples = samples; |
| |
| fData.ComputeMonochrome (); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_rgb_table::GetStream (dng_stream &stream) |
| { |
| |
| table_data data; |
| |
| if (stream.Get_uint32 () != btt_RGBTable) |
| { |
| return false; |
| } |
| |
| if (stream.Get_uint32 () != kRGBTableVersion) |
| { |
| ThrowBadFormat ("Unknown RGB table version"); |
| } |
| |
| data.fDimensions = stream.Get_uint32 (); |
| |
| data.fDivisions = stream.Get_uint32 (); |
| |
| if (data.fDimensions == 1) |
| { |
| |
| if (data.fDivisions < kMinDivisions1D || |
| data.fDivisions > kMaxDivisions1D) |
| { |
| ThrowBadFormat ("Invalid 1D divisions"); |
| } |
| |
| } |
| |
| else if (data.fDimensions == 3) |
| { |
| |
| if (data.fDivisions < kMinDivisions3D || |
| data.fDivisions > kMaxDivisions3D) |
| { |
| ThrowBadFormat ("Invalid 3D divisions"); |
| } |
| |
| } |
| |
| else |
| { |
| ThrowBadFormat ("Invalid dimensions"); |
| } |
| |
| uint16 nopValue [kMaxDivisions1D > kMaxDivisions3D ? kMaxDivisions1D |
| : kMaxDivisions3D]; |
| |
| for (uint32 index = 0; index < data.fDivisions; index++) |
| { |
| |
| nopValue [index] = (uint16) |
| ((index * 0x0FFFF + (data.fDivisions >> 1)) / |
| (data.fDivisions - 1)); |
| |
| } |
| |
| if (data.fDimensions == 1) |
| { |
| |
| data.fSamples.Allocate (data.fDivisions * 4 * sizeof (uint16)); |
| |
| uint16 *samples = data.fSamples.Buffer_uint16 (); |
| |
| for (uint32 index = 0; index < data.fDivisions; index++) |
| { |
| |
| samples [0] = stream.Get_uint16 () + nopValue [index]; |
| samples [1] = stream.Get_uint16 () + nopValue [index]; |
| samples [2] = stream.Get_uint16 () + nopValue [index]; |
| samples [3] = 0; |
| |
| samples += 4; |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| data.fSamples.Allocate (data.fDivisions * |
| data.fDivisions * |
| data.fDivisions * 4 * sizeof (uint16)); |
| |
| uint16 *samples = data.fSamples.Buffer_uint16 (); |
| |
| for (uint32 rIndex = 0; rIndex < data.fDivisions; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < data.fDivisions; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < data.fDivisions; bIndex++) |
| { |
| |
| samples [0] = stream.Get_uint16 () + nopValue [rIndex]; |
| samples [1] = stream.Get_uint16 () + nopValue [gIndex]; |
| samples [2] = stream.Get_uint16 () + nopValue [bIndex]; |
| samples [3] = 0; |
| |
| samples += 4; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| uint32 primaries = stream.Get_uint32 (); |
| |
| if (primaries >= primaries_count) |
| { |
| ThrowBadFormat ("Unknown RGB table primaries"); |
| } |
| |
| data.fPrimaries = (primaries_enum) primaries; |
| |
| uint32 gamma = stream.Get_uint32 (); |
| |
| if (gamma >= gamma_count) |
| { |
| ThrowBadFormat ("Unknown RGB table gamma"); |
| } |
| |
| data.fGamma = (gamma_enum) gamma; |
| |
| uint32 gamut = stream.Get_uint32 (); |
| |
| if (gamut >= gamut_count) |
| { |
| ThrowBadFormat ("Unknown RGB table gamut processing option"); |
| } |
| |
| data.fGamut = (gamut_enum) gamut; |
| |
| data.fMinAmount = stream.Get_real64 (); |
| data.fMaxAmount = stream.Get_real64 (); |
| |
| if (data.fMinAmount < 0.0 || data.fMinAmount > 1.0 || data.fMaxAmount < 1.0) |
| { |
| ThrowBadFormat ("Invalid min/max amount for RGB table"); |
| } |
| |
| data.ComputeMonochrome (); |
| |
| if (stream.Position () + 4 <= stream.Length ()) |
| { |
| data.fFlags = stream.Get_uint32 (); |
| } |
| |
| fData = data; |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_rgb_table::PutStream (dng_stream &stream, |
| bool /* forFingerprint */) const |
| { |
| |
| DNG_REQUIRE (IsValid (), "Invalid RGB Table"); |
| |
| stream.Put_uint32 (btt_RGBTable); |
| |
| stream.Put_uint32 (kRGBTableVersion); |
| |
| stream.Put_uint32 (fData.fDimensions); |
| |
| stream.Put_uint32 (fData.fDivisions); |
| |
| uint16 nopValue [kMaxDivisions1D > kMaxDivisions3D_InMemory ? kMaxDivisions1D |
| : kMaxDivisions3D_InMemory]; |
| |
| for (uint32 index = 0; index < fData.fDivisions; index++) |
| { |
| |
| nopValue [index] = (uint16) |
| ((index * 0x0FFFF + (fData.fDivisions >> 1)) / |
| (fData.fDivisions - 1)); |
| |
| } |
| |
| const uint16 *samples = fData.fSamples.Buffer_uint16 (); |
| |
| if (fData.fDimensions == 1) |
| { |
| |
| for (uint32 index = 0; index < fData.fDivisions; index++) |
| { |
| |
| stream.Put_uint16 (samples [0] - nopValue [index]); |
| stream.Put_uint16 (samples [1] - nopValue [index]); |
| stream.Put_uint16 (samples [2] - nopValue [index]); |
| |
| samples += 4; |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| for (uint32 rIndex = 0; rIndex < fData.fDivisions; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < fData.fDivisions; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < fData.fDivisions; bIndex++) |
| { |
| |
| stream.Put_uint16 (samples [0] - nopValue [rIndex]); |
| stream.Put_uint16 (samples [1] - nopValue [gIndex]); |
| stream.Put_uint16 (samples [2] - nopValue [bIndex]); |
| |
| samples += 4; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| stream.Put_uint32 ((uint32) fData.fPrimaries); |
| |
| stream.Put_uint32 ((uint32) fData.fGamma); |
| |
| stream.Put_uint32 ((uint32) fData.fGamut); |
| |
| stream.Put_real64 (fData.fMinAmount); |
| stream.Put_real64 (fData.fMaxAmount); |
| |
| if (fData.fFlags != 0) |
| { |
| |
| stream.Put_uint32 (fData.fFlags); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_image_table_compression_info::~dng_image_table_compression_info () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table_compression_info::Compress (dng_host &host, |
| dng_stream &stream, |
| const dng_image &image) const |
| { |
| |
| dng_image_writer writer; |
| |
| writer.WriteTIFFWithProfile (host, |
| stream, |
| image, |
| image.Planes () >= 3 |
| ? piRGB |
| : piBlackIsZero, |
| image.PixelType () == ttShort |
| ? ccJPEG // Lossless JPEG |
| : ccDeflate); |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_image_table_jxl_compression_info::dng_image_table_jxl_compression_info () |
| |
| : fEncodeSettings (new dng_jxl_encode_settings) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table_jxl_compression_info::Compress (dng_host &host, |
| dng_stream &stream, |
| const dng_image &image) const |
| { |
| |
| DNG_REQUIRE (fEncodeSettings.Get (), |
| "Missing encode settings"); |
| |
| #if 1 |
| |
| // Use TIFF container but with JXL compression. This is better for larger |
| // images since we can encode tiles in parallel. |
| |
| host.SetJXLEncodeSettings (*fEncodeSettings); |
| |
| host.SetJXLColorSpaceInfo (fColorSpaceInfo); |
| |
| dng_image_writer writer; |
| |
| writer.WriteTIFFWithProfile (host, |
| stream, |
| image, |
| image.Planes () >= 3 |
| ? piRGB |
| : piBlackIsZero, |
| ccJXL, |
| nullptr, // metadata |
| nullptr, // profile data |
| 0, // profile size, |
| nullptr, // resolution |
| nullptr, // thumbnail |
| nullptr, // image resources |
| kMetadataSubset_All, |
| false, // has transparency |
| true, // allow big tiff |
| nullptr, // gain map, |
| nullptr, // gain map metadata block |
| fPreferHalfFloat); |
| |
| #else |
| |
| // Use JXL directly. |
| |
| dng_jxl_color_space_info colorSpaceInfo; |
| |
| PreviewColorSpaceToJXLEncoding (previewColorSpace_MaxEnum, |
| image.Planes (), |
| colorSpaceInfo); |
| |
| EncodeJXL_Tile (host, |
| stream, |
| image, |
| colorSpaceInfo, |
| *fEncodeSettings); |
| |
| #endif |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_image_table::dng_image_table () |
| |
| : dng_big_table (&gImageTableCache) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_image_table::dng_image_table (const dng_image_table &table) |
| |
| : dng_big_table (table) |
| |
| , fImage (table.fImage) |
| |
| , fCompressedData (table.fCompressedData) |
| |
| , fCompressionType (table.fCompressionType) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_image_table & dng_image_table::operator= (const dng_image_table &table) |
| { |
| |
| dng_big_table::operator= (table); |
| |
| fImage = table.fImage; |
| |
| fCompressedData = table.fCompressedData; |
| |
| fCompressionType = table.fCompressionType; |
| |
| return *this; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_image_table::~dng_image_table () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_image_table::IsValid () const |
| { |
| |
| if (IsMissing ()) |
| { |
| return false; |
| } |
| |
| if (!fImage.get ()) |
| { |
| return false; |
| } |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::SetInvalid () |
| { |
| |
| *this = dng_image_table (); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::SetImage (const dng_image *image, |
| const dng_image_table_compression_info *compressionInfo, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| fImage = std::shared_ptr<const dng_image> (image); |
| |
| fCompressedData.reset (); |
| |
| if (compressionInfo && (compressionInfo->Type () > 0)) |
| CompressImage (*compressionInfo, |
| sniffer); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::SetImage (const std::shared_ptr<const dng_image> &image, |
| const dng_image_table_compression_info *compressionInfo, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| if (fImage != image) |
| { |
| |
| fImage = image; |
| |
| fCompressedData.reset (); |
| |
| if (compressionInfo && (compressionInfo->Type () > 0)) |
| CompressImage (*compressionInfo, |
| sniffer); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_host * dng_image_table::MakeHost (dng_abort_sniffer *sniffer) const |
| { |
| |
| return new dng_host (nullptr, sniffer); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_image_table::ComputeFingerprint () const |
| { |
| |
| // If we're using lossy compression, then fingerprint the (compressed) |
| // stream itself, including the header. |
| |
| if (fCompressedData) |
| { |
| |
| AutoPtr<dng_host> host (MakeHost (nullptr)); |
| |
| dng_memory_stream tempStream (host->Allocator ()); |
| |
| tempStream.SetLittleEndian (); |
| |
| PutStream (tempStream, true); |
| |
| tempStream.Flush (); |
| |
| tempStream.SetReadPosition (0); |
| |
| dng_md5_printer_le_stream stream; |
| |
| tempStream.CopyToStream (stream, |
| tempStream.Length ()); |
| |
| auto digest = stream.Result (); |
| |
| return digest; |
| |
| } |
| |
| // Otherwise fingerprint the image itself (uncompressed case). |
| |
| if (fImage.get ()) |
| { |
| |
| AutoPtr<dng_host> host (MakeHost (nullptr)); |
| |
| dng_md5_printer_le_stream stream; |
| |
| stream.Put_uint32 (btt_ImageTable); |
| |
| stream.Put_uint32 (kImageTableVersion); |
| |
| stream.Put_int32 (fImage->Bounds ().t); |
| stream.Put_int32 (fImage->Bounds ().l); |
| stream.Put_int32 (fImage->Bounds ().b); |
| stream.Put_int32 (fImage->Bounds ().r); |
| |
| stream.Put_uint32 (fImage->Planes ()); |
| |
| stream.Put_uint32 (fImage->PixelType ()); |
| |
| dng_fingerprint imageDigest = dng_negative::FindFastImageDigest |
| (*host, |
| *fImage, |
| fImage->PixelType ()); |
| |
| stream.Put (imageDigest); |
| |
| return stream.Result (); |
| |
| } |
| |
| return dng_fingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static void CheckImageTableIFD (const dng_ifd &ifd) |
| { |
| |
| dng_rect bounds = ifd.Bounds (); |
| |
| if (bounds.ShortSide () < 1 || |
| bounds.LongSide () > kMaxImageSide) |
| { |
| ThrowBadFormat (); |
| } |
| |
| uint32 planes = ifd.fSamplesPerPixel; |
| |
| if (planes < 1 || planes > kMaxColorPlanes) |
| { |
| ThrowBadFormat (); |
| } |
| |
| uint32 pixelType = ifd.PixelType (); |
| |
| if (pixelType != ttByte && |
| pixelType != ttShort && |
| pixelType != ttFloat) |
| { |
| ThrowBadFormat (); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_image_table::GetStream (dng_stream &stream) |
| { |
| |
| AutoPtr<dng_host> host (MakeHost (stream.Sniffer ())); |
| |
| if (stream.Get_uint32 () != btt_ImageTable) |
| { |
| return false; |
| } |
| |
| if (stream.Get_uint32 () != kImageTableVersion) |
| { |
| ThrowBadFormat ("Unknown image table version"); |
| } |
| |
| dng_point imageTL; |
| |
| imageTL.v = stream.Get_int32 (); |
| imageTL.h = stream.Get_int32 (); |
| |
| if (!stream.Data ()) |
| { |
| ThrowProgramError ("Not a memory stream"); |
| } |
| |
| dng_stream subStream ((uint8 *) stream.Data () + stream.Position (), |
| (uint32) (stream.Length () - stream.Position ())); |
| |
| subStream.SetSniffer (stream.Sniffer ()); |
| |
| AutoPtr<dng_image> image; |
| |
| dng_info jxlInfo; |
| |
| if (ParseJXL (*host, |
| subStream, |
| jxlInfo, |
| true, // bare codestream |
| false)) // container |
| { |
| |
| // Read as JXL. |
| |
| if (jxlInfo.IFDCount () < 1) |
| { |
| ThrowBadFormat (); |
| } |
| |
| CheckImageTableIFD (*jxlInfo.fIFD [0]); |
| |
| subStream.SetReadPosition (0); |
| |
| dng_jxl_decoder decoder; |
| |
| decoder.fNeedBoxMeta = false; |
| |
| decoder.Decode (*host, subStream); |
| |
| image.Reset (decoder.fMainImage.Release ()); |
| |
| fCompressionType = ccJXL; |
| |
| } |
| |
| else |
| { |
| |
| // Read as TIFF. |
| |
| dng_info info; |
| |
| info.Parse (*host, subStream); |
| |
| info.PostParse (*host); |
| |
| if (info.fMagic != 42) |
| { |
| ThrowBadFormat (); |
| } |
| |
| if (info.IFDCount () < 1) |
| { |
| ThrowBadFormat (); |
| } |
| |
| const dng_ifd &ifd = *info.fIFD [0]; |
| |
| CheckImageTableIFD (ifd); |
| |
| image.Reset (host->Make_dng_image (ifd.Bounds (), |
| ifd.fSamplesPerPixel, |
| ifd.PixelType ())); |
| |
| ifd.ReadImage (*host, |
| subStream, |
| *image); |
| |
| fCompressionType = ifd.fCompression; |
| |
| } // JXL vs TIFF |
| |
| // Grab a copy of the (lossy) compressed data. |
| |
| if (fCompressionType == ccJXL) |
| { |
| |
| subStream.SetReadPosition (0); |
| |
| fCompressedData.reset (subStream.AsMemoryBlock (host->Allocator ())); |
| |
| } |
| |
| if (imageTL != dng_point (0, 0)) |
| { |
| |
| AutoPtr<dng_image> tempImage (image->Clone ()); |
| |
| tempImage->Offset (imageTL); |
| |
| image.Reset (tempImage.Release ()); |
| |
| } |
| |
| fImage = std::shared_ptr<const dng_image> (image.Release ()); |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::PutStream (dng_stream &stream, |
| bool forFingerprint) const |
| { |
| |
| dng_image_table_compression_info defaultInfo; |
| |
| PutCompressedStream (stream, |
| forFingerprint, |
| defaultInfo); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::PutCompressedStream (dng_stream &stream, |
| bool /* forFingerprint */, |
| const dng_image_table_compression_info &info) const |
| { |
| |
| AutoPtr<dng_host> host (MakeHost (stream.Sniffer ())); |
| |
| stream.Put_uint32 (btt_ImageTable); |
| |
| stream.Put_uint32 (kImageTableVersion); |
| |
| stream.Put_int32 (fImage->Bounds ().t); |
| stream.Put_int32 (fImage->Bounds ().l); |
| |
| const dng_image *tiffImage = fImage.get (); |
| |
| AutoPtr<dng_image> tempImage; |
| |
| if (tiffImage->Bounds ().TL () != dng_point (0, 0)) |
| { |
| |
| tempImage.Reset (tiffImage->Clone ()); |
| |
| tempImage->Offset (-fImage->Bounds ().TL ()); |
| |
| tiffImage = tempImage.Get (); |
| |
| } |
| |
| // If we have compressed data, then just write that directly. |
| |
| if (fCompressedData) |
| { |
| |
| // printf ("--- writing compressed\n"); |
| |
| stream.Put (fCompressedData->Buffer (), |
| fCompressedData->LogicalSize ()); |
| |
| } |
| |
| // Otherwise use the provided compression info. |
| |
| else |
| { |
| |
| dng_memory_stream tempStream (host->Allocator ()); |
| |
| tempStream.SetLittleEndian (); |
| |
| info.Compress (*host, |
| tempStream, |
| *tiffImage); |
| |
| // Remember the compressed data. |
| |
| if (info.Type () != 0) |
| { |
| |
| tempStream.SetReadPosition (0); |
| |
| fCompressedData.reset (tempStream.AsMemoryBlock (host->Allocator ())); |
| |
| } |
| |
| tempStream.SetReadPosition (0); |
| |
| tempStream.CopyToStream (stream, tempStream.Length ()); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::CompressImage (const dng_image_table_compression_info &info, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| fCompressionType = info.Type (); |
| |
| if (!fImage || |
| info.Type () == 0 || |
| info.Type () == ccUncompressed) |
| { |
| return; |
| } |
| |
| // Force the image go thru a write-read (encode-decode) cycle so that the |
| // image stored in this object reflects errors introduced by the lossy |
| // codec. |
| |
| AutoPtr<dng_host> host (MakeHost (sniffer)); |
| |
| dng_memory_stream tempStream (host->Allocator ()); |
| |
| tempStream.SetLittleEndian (); |
| |
| tempStream.SetSniffer (sniffer); |
| |
| PutCompressedStream (tempStream, |
| false, |
| info); |
| |
| // Cannot just reset tempStream read position to 0 and call GetStream on |
| // it directly because dng_image_table::GetStream implementation currently |
| // relies on the stream having the data in one contiguous chunk. So make a |
| // copy of the data and then feed the result to GetStream. |
| |
| AutoPtr<dng_memory_block> block (tempStream.AsMemoryBlock (host->Allocator ())); |
| |
| dng_stream readStream (block->Buffer (), |
| block->LogicalSize ()); |
| |
| readStream.SetSniffer (sniffer); |
| |
| GetStream (readStream); |
| |
| fCompressionType = info.Type (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::SetData (const dng_image_table_data &data) |
| { |
| |
| fImage = data.fImage; |
| |
| fCompressedData = data.fCompressedData; |
| |
| fCompressionType = data.fCompressionType; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_image_table::GetData (dng_image_table_data &data) const |
| { |
| |
| data.fImage = fImage; |
| |
| data.fCompressedData = fCompressedData; |
| |
| data.fCompressionType = fCompressionType; |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_packed_image_table::dng_packed_image_table () |
| |
| : dng_big_table (&gPackedImageTableCache) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_packed_image_table::dng_packed_image_table (const dng_packed_image_table &table) |
| |
| : dng_big_table (table) |
| |
| , fTableDigest (table.fTableDigest) |
| , fTable (table.CloneTable ()) |
| , fBlock (table.fBlock) |
| |
| , fSize (table.fSize) |
| , fPlanes (table.fPlanes) |
| , fPixelType (table.fPixelType) |
| |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_packed_image_table & dng_packed_image_table::operator= (const dng_packed_image_table &table) |
| { |
| |
| dng_big_table::operator= (table); |
| |
| fTableDigest = table.fTableDigest; |
| fBlock = table.fBlock; |
| |
| fTable.reset (table.CloneTable ()); |
| |
| fSize = table.fSize; |
| fPlanes = table.fPlanes; |
| fPixelType = table.fPixelType; |
| |
| return *this; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| const dng_image_table & dng_packed_image_table::Table () const |
| { |
| |
| DNG_REQUIRE (fTable, "Invalid table"); |
| |
| return *fTable; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_packed_image_table::IsValid () const |
| { |
| |
| return (fTableDigest.IsValid () && |
| ((fTable && fTable->IsValid ()) || (fBlock != nullptr))); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| uint32 dng_packed_image_table::PackedBytes () const |
| { |
| |
| return fBlock ? fBlock->LogicalSize () : 0; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_host * dng_packed_image_table::MakeHost (dng_abort_sniffer *sniffer) const |
| { |
| |
| return new dng_host (nullptr, sniffer); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_image_table * dng_packed_image_table::MakeTable () const |
| { |
| |
| return new dng_image_table; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_image_table * dng_packed_image_table::CloneTable () const |
| { |
| |
| return fTable ? (new dng_image_table (*fTable)) : nullptr; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::Clear () |
| { |
| |
| fTableDigest.Clear (); |
| |
| fTable.reset (); |
| |
| fBlock.reset (); |
| |
| fSize = dng_point (); |
| |
| fPlanes = 0; |
| |
| fPixelType = 0; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::ClearPackedData () |
| { |
| |
| fBlock.reset (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::SetImage (const dng_image *image, |
| const dng_image_table_compression_info *compressionInfo, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| if (!image) |
| { |
| Clear (); |
| return; |
| } |
| |
| AutoPtr<dng_image_table> table (MakeTable ()); |
| |
| fSize = image->Size (); |
| fPlanes = image->Planes (); |
| fPixelType = image->PixelType (); |
| |
| table->SetImage (image, compressionInfo, sniffer); |
| |
| fTable.reset (table.Release ()); |
| |
| fTableDigest = fTable->Fingerprint (); |
| |
| ClearPackedData (); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::SetImage (const std::shared_ptr<const dng_image> &image, |
| const dng_image_table_compression_info *compressionInfo, |
| dng_abort_sniffer *sniffer) |
| { |
| |
| if (!image) |
| { |
| Clear (); |
| return; |
| } |
| |
| if (fTable && (fTable->ShareImage () == image)) |
| return; |
| |
| fSize = image->Size (); |
| fPlanes = image->Planes (); |
| fPixelType = image->PixelType (); |
| |
| AutoPtr<dng_image_table> table (MakeTable ()); |
| |
| table->SetImage (image, compressionInfo, sniffer); |
| |
| fTable.reset (table.Release ()); |
| |
| fTableDigest = fTable->Fingerprint (); |
| |
| ClearPackedData (); |
| |
| RecomputeFingerprint (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::Unpack (dng_abort_sniffer *sniffer) |
| { |
| |
| DNG_REQUIRE (fBlock, |
| "Cannot unpack invalid block"); |
| |
| AutoPtr<dng_host> host (MakeHost (sniffer)); |
| |
| AutoPtr<dng_image_table> table (MakeTable ()); |
| |
| if (!table->DecodeFromBinary (*host, |
| fBlock->Buffer_uint8 (), |
| fBlock->LogicalSize ())) |
| { |
| |
| ThrowBadFormat ("Could not Unpack block to cr_image_table"); |
| |
| } |
| |
| fTable.reset (table.Release ()); |
| |
| if (fTable->Fingerprint () != fTableDigest) |
| { |
| |
| DNG_REPORT ("fTableDigest does not match table fingerprint"); |
| |
| } |
| |
| #if 0 |
| fSize = Image ().Size (); |
| fPlanes = Image ().Planes (); |
| fPixelType = Image ().Size (); |
| #endif |
| |
| ClearPackedData (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::Pack (dng_abort_sniffer *sniffer) |
| { |
| |
| DNG_REQUIRE (IsValidUnpacked (), |
| "Cannot pack invalid table"); |
| |
| AutoPtr<dng_host> host (MakeHost (sniffer)); |
| |
| uint32 compressedSize = 0; |
| |
| fBlock.reset (fTable->EncodeAsBinary (host->Allocator (), |
| compressedSize)); |
| |
| fTable.reset (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_packed_image_table::GetStream (dng_stream &stream) |
| { |
| |
| if (stream.Get_uint32 () != btt_PackedImageTable) |
| return false; |
| |
| if (stream.Get_uint32 () != kPackedImageTableVersion) |
| ThrowBadFormat ("Unknown packed image table version"); |
| |
| // Clear the table. |
| |
| fTable.reset (); |
| |
| // Read digest. |
| |
| stream.Get (fTableDigest); |
| |
| // Read size. |
| |
| fSize.h = int32 (stream.Get_uint32 ()); |
| fSize.v = int32 (stream.Get_uint32 ()); |
| |
| if (fSize.h <= 0) |
| ThrowBadFormat ("Invalid size.h in packed image table"); |
| |
| if (fSize.v <= 0) |
| ThrowBadFormat ("Invalid size.v in packed image table"); |
| |
| // Read planes. |
| |
| fPlanes = stream.Get_uint32 (); |
| |
| if (fPlanes == 0 || fPlanes > kMaxColorPlanes) |
| ThrowBadFormat ("Invalid planes in packed image table"); |
| |
| // Read pixel type. |
| |
| fPixelType = stream.Get_uint32 (); |
| |
| if (fPixelType == 0) |
| ThrowBadFormat ("Invalid pixel type in packed image table"); |
| |
| // Read bytes of image table. Currently limit compressed image tables |
| // to 32-bit. |
| |
| const uint32 bytes = stream.Get_uint32 (); |
| |
| AutoPtr<dng_host> host (MakeHost (stream.Sniffer ())); |
| |
| AutoPtr<dng_memory_block> block (host->Allocate (bytes)); |
| |
| stream.Get (block->Buffer (), bytes); |
| |
| fBlock.reset (block.Release ()); |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_packed_image_table::PutStream (dng_stream &stream, |
| bool /* forFingerprint */) const |
| { |
| |
| DNG_REQUIRE (IsValid (), |
| "Called PutStream on invalid packed image table"); |
| |
| // Write Big Table Type. |
| |
| stream.Put_uint32 (btt_PackedImageTable); |
| |
| // Write Version. |
| |
| stream.Put_uint32 (kPackedImageTableVersion); |
| |
| // Write digest. |
| |
| stream.Put (fTableDigest); |
| |
| // Write properties. |
| |
| stream.Put_uint32 (uint32 (fSize.h)); |
| stream.Put_uint32 (uint32 (fSize.v)); |
| |
| stream.Put_uint32 (fPlanes); |
| |
| stream.Put_uint32 (fPixelType); |
| |
| // If the block is valid, just use that. |
| |
| std::shared_ptr<const dng_memory_block> block; |
| |
| if (fBlock) |
| block = fBlock; |
| |
| // Otherwise, first encode the table into a temporary block. |
| |
| else |
| { |
| |
| DNG_REQUIRE (fTable, "missing fTable"); |
| |
| AutoPtr<dng_host> host (MakeHost (stream.Sniffer ())); |
| |
| uint32 compressedSize = 0; |
| |
| block.reset |
| (fTable->EncodeAsBinary (host->Allocator (), |
| compressedSize)); |
| |
| } |
| |
| DNG_REQUIRE (block, |
| "Missing block"); |
| |
| // Write byte length. |
| |
| stream.Put_uint32 (block->LogicalSize ()); |
| |
| // Write data. |
| |
| stream.Put (block->Buffer (), |
| block->LogicalSize ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_masked_rgb_table::dng_masked_rgb_table () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_masked_rgb_table::dng_masked_rgb_table (dng_string tableSemanticName, |
| uint32 pixelType, |
| const dng_rgb_table &table) |
| |
| : fTableSemanticName (tableSemanticName) |
| |
| , fPixelType (pixelType) |
| |
| , fTable (table) |
| |
| { |
| |
| Validate (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::Validate () const |
| { |
| |
| // Name cannot be longer than 16-bit. |
| |
| if (fTableSemanticName.Length () > 65535) |
| { |
| ThrowBadFormat ("TableSemanticName too long in RGBTables"); |
| } |
| |
| // Check PixelType. |
| |
| CheckPixelType (fPixelType); |
| |
| // Check Divisions. |
| |
| CheckDivisions (fTable.Divisions ()); |
| |
| // Check GammaEncoding. |
| |
| CheckGammaEncoding (fTable.Gamma ()); |
| |
| // Check ColorPrimaries. |
| |
| CheckColorPrimaries (fTable.Primaries ()); |
| |
| // Check GamutExtension. |
| |
| CheckGamutExtension (fTable.Gamut ()); |
| |
| // Must be 3D table. |
| |
| DNG_REQUIRE (fTable.Dimensions () == 3, |
| "RGBTables must have dimension value of 3"); |
| |
| // For now don't perform any validation on the table data itself. |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static uint8 ColorPrimariesEnumToRGBTablesTagValue |
| (dng_rgb_table::primaries_enum value) |
| { |
| |
| switch (value) |
| { |
| |
| case dng_rgb_table::primaries_sRGB: return 0; |
| case dng_rgb_table::primaries_Adobe: return 1; |
| case dng_rgb_table::primaries_P3: return 2; |
| case dng_rgb_table::primaries_Rec2020: return 3; |
| case dng_rgb_table::primaries_ProPhoto: return 4; |
| |
| default: |
| ThrowProgramError ("unsupported primaries_enum value"); |
| break; |
| |
| } |
| |
| return 0; // sRGB |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static dng_rgb_table::primaries_enum ColorPrimariesValueToEnum (uint32 x) |
| { |
| |
| switch (x) |
| { |
| |
| case 0: return dng_rgb_table::primaries_sRGB; |
| case 1: return dng_rgb_table::primaries_Adobe; |
| case 2: return dng_rgb_table::primaries_P3; |
| case 3: return dng_rgb_table::primaries_Rec2020; |
| case 4: return dng_rgb_table::primaries_ProPhoto; |
| |
| default: |
| { |
| ThrowProgramError ("unsupported ColorPrimaries value"); |
| break; |
| } |
| |
| } |
| |
| return dng_rgb_table::primaries_sRGB; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static uint32 GamutExtensionEnumToRGBTablesTagValue (dng_rgb_table::gamut_enum value) |
| { |
| |
| switch (value) |
| { |
| |
| case dng_rgb_table::gamut_clip: return 0; |
| case dng_rgb_table::gamut_extend: return 1; |
| |
| default: |
| ThrowProgramError ("unsupported gamut_enum value"); |
| break; |
| |
| } |
| |
| return 1; // extend |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static dng_rgb_table::gamut_enum GamutValueToEnum (uint32 x) |
| { |
| |
| switch (x) |
| { |
| |
| case 0: return dng_rgb_table::gamut_clip; |
| case 1: return dng_rgb_table::gamut_extend; |
| |
| default: |
| { |
| ThrowProgramError ("Unexpected GamutExtension value"); |
| break; |
| } |
| |
| } |
| |
| return dng_rgb_table::gamut_extend; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static uint8 GammaEnumToRGBTablesTagValue (dng_rgb_table::gamma_enum value) |
| { |
| |
| switch (value) |
| { |
| |
| case dng_rgb_table::gamma_Linear: return 0; |
| case dng_rgb_table::gamma_sRGB: return 1; |
| case dng_rgb_table::gamma_1_8: return 2; |
| case dng_rgb_table::gamma_2_2: return 3; |
| case dng_rgb_table::gamma_Rec2020: return 4; |
| |
| default: |
| ThrowProgramError ("unsupported gamma_enum value"); |
| break; |
| |
| } |
| |
| return 1; // sRGB |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static dng_rgb_table::gamma_enum GammaValueToEnum (uint32 x) |
| { |
| |
| switch (x) |
| { |
| |
| case 0: return dng_rgb_table::gamma_Linear; |
| case 1: return dng_rgb_table::gamma_sRGB; |
| case 2: return dng_rgb_table::gamma_1_8; |
| case 3: return dng_rgb_table::gamma_2_2; |
| case 4: return dng_rgb_table::gamma_Rec2020; |
| |
| default: |
| { |
| ThrowProgramError ("Unexpected GammaEncoding value"); |
| break; |
| } |
| |
| } |
| |
| return dng_rgb_table::gamma_sRGB; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::CheckDivisions (uint32 divisions) |
| { |
| |
| if (divisions < 2 || divisions > 32) |
| { |
| ThrowBadFormat ("Invalid Divisions in RGBTables"); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::CheckPixelType (uint32 pixelType) |
| { |
| |
| if (pixelType > 2) |
| { |
| ThrowBadFormat ("Invalid PixelType in RGBTables"); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::CheckGammaEncoding (dng_rgb_table::gamma_enum gamma) |
| { |
| |
| switch (gamma) |
| { |
| |
| // Enumerate the ones supported in the tag. |
| |
| case dng_rgb_table::gamma_Linear: |
| case dng_rgb_table::gamma_sRGB: |
| case dng_rgb_table::gamma_1_8: |
| case dng_rgb_table::gamma_2_2: |
| case dng_rgb_table::gamma_Rec2020: |
| break; |
| |
| // Reject everything else. |
| |
| default: |
| ThrowBadFormat ("Unsupported GammaEncoding in RGBTables"); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::CheckColorPrimaries (dng_rgb_table::primaries_enum primaries) |
| { |
| |
| switch (primaries) |
| { |
| |
| // Enumerate the ones supported in the tag. |
| |
| case dng_rgb_table::primaries_Adobe: |
| case dng_rgb_table::primaries_P3: |
| case dng_rgb_table::primaries_ProPhoto: |
| case dng_rgb_table::primaries_Rec2020: |
| case dng_rgb_table::primaries_sRGB: |
| break; |
| |
| // Reject everything else. |
| |
| default: |
| ThrowBadFormat ("Unsupported ColorPrimaries in RGBTables"); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::CheckGamutExtension (dng_rgb_table::gamut_enum gamut) |
| { |
| |
| switch (gamut) |
| { |
| |
| // Enumerate the ones supported in the tag. |
| |
| case dng_rgb_table::gamut_clip: |
| case dng_rgb_table::gamut_extend: |
| break; |
| |
| // Reject everything else. |
| |
| default: |
| ThrowBadFormat ("Unsupported GamutExtension in RGBTables"); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::GetStream (dng_host &host, |
| dng_stream &stream) |
| { |
| |
| // Read LengthTableSemanticName and TableSemanticName. |
| |
| uint16 nameLen = stream.Get_uint16 (); |
| |
| dng_memory_data nameData (nameLen + 1); |
| |
| stream.Get (nameData.Buffer (), nameLen); |
| |
| nameData.Buffer_uint8 () [nameLen] = 0; // add null-term char |
| |
| fTableSemanticName.Set (nameData.Buffer_char ()); |
| |
| // Read Divisions. |
| |
| uint32 divisions = (uint32) stream.Get_uint8 (); |
| |
| CheckDivisions (divisions); |
| |
| // Read PixelType. |
| |
| fPixelType = (uint32) stream.Get_uint8 (); |
| |
| CheckPixelType (fPixelType); |
| |
| // Read GammaEncoding. |
| |
| uint32 gamma = (uint32) stream.Get_uint8 (); |
| |
| auto gammaEnum = GammaValueToEnum (gamma); |
| |
| CheckGammaEncoding (gammaEnum); |
| |
| // Read ColorPrimaries. |
| |
| uint32 primaries = (uint32) stream.Get_uint8 (); |
| |
| auto primariesEnum = ColorPrimariesValueToEnum (primaries); |
| |
| CheckColorPrimaries (primariesEnum); |
| |
| // Read GamutExtension. |
| |
| uint32 gamut = (uint32) stream.Get_uint8 (); |
| |
| auto gamutEnum = GamutValueToEnum (gamut); |
| |
| CheckGamutExtension (gamutEnum); |
| |
| // Read samples. |
| |
| uint32 srcPixelSize = 1; |
| |
| if (fPixelType == 1) srcPixelSize = 2; |
| else if (fPixelType == 2) srcPixelSize = 4; |
| |
| uint32 srcBytes = divisions * divisions * divisions * 3 * srcPixelSize; |
| |
| uint32 dstBytes = divisions * divisions * divisions * 4 * 2; |
| |
| dng_ref_counted_block samples; |
| |
| samples.Allocate (dstBytes); |
| |
| uint16 *dst = samples.Buffer_uint16 (); |
| |
| const uint32 divs = divisions; |
| |
| // Read into a stored data block. |
| |
| fStoredData.reset (host.Allocate (srcBytes)); |
| |
| stream.Get (fStoredData->Buffer (), |
| srcBytes); |
| |
| if (fPixelType == 0) // u8 |
| { |
| |
| const uint8 *src = fStoredData->Buffer_uint8 (); |
| |
| constexpr uint16 scale8to16 = 257; // 65535 / 255 |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| dst [0] = uint16 (src [0]) * scale8to16; |
| dst [1] = uint16 (src [1]) * scale8to16; |
| dst [2] = uint16 (src [2]) * scale8to16; |
| dst [3] = 0; |
| |
| src += 3; |
| dst += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else if (fPixelType == 1) // u16 |
| { |
| |
| const uint16 *src = fStoredData->Buffer_uint16 (); |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| dst [0] = src [0]; |
| dst [1] = src [1]; |
| dst [2] = src [2]; |
| dst [3] = 0; |
| |
| src += 3; |
| dst += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else if (fPixelType == 2) // fp32 |
| { |
| |
| // Map 1.0f to 65535. |
| |
| const real32 *src = fStoredData->Buffer_real32 (); |
| |
| const real32 kScale = 65535.0f; |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| dst [0] = Round_int32 (kScale * src [0]); |
| dst [1] = Round_int32 (kScale * src [1]); |
| dst [2] = Round_int32 (kScale * src [2]); |
| dst [3] = 0; |
| |
| src += 3; |
| dst += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else |
| { |
| |
| ThrowProgramError ("Unexpected fPixelType"); |
| |
| } |
| |
| const uint32 kDimensions = 3; |
| |
| fTable.Set (kDimensions, |
| divisions, |
| samples); |
| |
| fTable.SetGamut (gamutEnum); |
| |
| fTable.SetGamma (gammaEnum); |
| |
| fTable.SetPrimaries (primariesEnum); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::PutStream (dng_stream &stream) const |
| { |
| |
| // LengthTableSemanticName. |
| |
| uint32 len = fTableSemanticName.Length (); |
| |
| stream.Put_uint16 ((uint16) len); |
| |
| // TableSemanticName. |
| |
| stream.Put (fTableSemanticName.Get (), len); |
| |
| // Divisions. |
| |
| stream.Put_uint8 ((uint8) fTable.Divisions ()); |
| |
| // PixelType. |
| |
| stream.Put_uint8 ((uint8) fPixelType); |
| |
| // GammaEncoding. |
| |
| stream.Put_uint8 (GammaEnumToRGBTablesTagValue (fTable.Gamma ())); |
| |
| // ColorPrimaries. |
| |
| stream.Put_uint8 (ColorPrimariesEnumToRGBTablesTagValue (fTable.Primaries ())); |
| |
| // GamutExtension. |
| |
| stream.Put_uint8 (GamutExtensionEnumToRGBTablesTagValue (fTable.Gamut ())); |
| |
| // Table sample data. |
| |
| // If we have stored data, just write that. |
| |
| if (fStoredData.get ()) |
| { |
| |
| stream.Put (fStoredData->Buffer (), |
| fStoredData->LogicalSize ()); |
| |
| } |
| |
| // Otherwise derive the data from the internal table representation. |
| |
| else |
| { |
| |
| // The data is stored in R, G, B order (R = outermost, B = innermost). The |
| // dng_rgb_table class already stores its samples in this order, so just |
| // walk through the values in linear order. |
| |
| const uint16 *samples = fTable.Samples (); |
| |
| const uint32 divs = fTable.Divisions (); |
| |
| if (fPixelType == 1) |
| { |
| |
| // Output format is 16-bit unsigned, which matches our internal |
| // representation, so no conversion needed. |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| stream.Put_uint16 (samples [0]); |
| stream.Put_uint16 (samples [1]); |
| stream.Put_uint16 (samples [2]); |
| |
| samples += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else if (fPixelType == 0) |
| { |
| |
| // Output format is 8-bit unsigned, so we need to convert. |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| stream.Put_uint8 (uint8 ((int32 (samples [0]) + 128) / 257)); |
| stream.Put_uint8 (uint8 ((int32 (samples [1]) + 128) / 257)); |
| stream.Put_uint8 (uint8 ((int32 (samples [2]) + 128) / 257)); |
| |
| samples += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else if (fPixelType == 2) |
| { |
| |
| // Output format is fp32, so we need to convert. |
| |
| const real32 kScale = 1.0f / 65535.0f; |
| |
| for (uint32 rIndex = 0; rIndex < divs; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| stream.Put_real32 (kScale * samples [0]); |
| stream.Put_real32 (kScale * samples [1]); |
| stream.Put_real32 (kScale * samples [2]); |
| |
| samples += 4; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| } |
| |
| else |
| { |
| |
| ThrowProgramError ("Unsupported PixelType in " |
| "dng_masked_rgb_table::PutStream"); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table::AddDigest (dng_md5_printer_stream &printer) const |
| { |
| |
| // Header. |
| |
| printer.ProcessPtr ("dng_masked_rgb_table", 20); |
| |
| // Name. |
| |
| uint32 nameLen = SemanticName ().Length (); |
| |
| printer.Put_uint32 (nameLen); |
| |
| if (nameLen > 0) |
| { |
| |
| printer.ProcessPtr (SemanticName ().Get (), nameLen); |
| |
| } |
| |
| // Pixel type. |
| |
| printer.Put_uint32 (fPixelType); |
| |
| // Table digest. |
| |
| dng_fingerprint tableDigest = fTable.Fingerprint (); |
| |
| printer.Process (tableDigest); |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table_render_data::Initialize (const dng_negative &negative, |
| const dng_camera_profile &profile) |
| { |
| |
| if (!profile.HasMaskedRGBTables ()) |
| return; |
| |
| auto maskedTablesReference = profile.ShareMaskedRGBTables (); |
| |
| const auto &tables = *maskedTablesReference; |
| |
| if (tables.IsNOP ()) |
| return; |
| |
| fUseSequentialMethod = tables.UseSequentialMethod (); |
| |
| fInputDigest.Clear (); // Null fingerprint for profile case |
| |
| // Find correspondence between RGBTables and SemanticMasks tags. It is |
| // still possible that we have a NOP situation if every table uses a mask |
| // name that is not found in SemanticMasks. |
| |
| // First, make a hashtable of semantic masks, with the name as a key. |
| |
| std::unordered_map<dng_string, |
| dng_semantic_mask, |
| dng_string_hash> smMap; |
| |
| { |
| |
| const uint32 numMasks = negative.NumSemanticMasks (); |
| |
| for (uint32 i = 0; i < numMasks; i++) |
| { |
| |
| const auto &mask = negative.SemanticMask (i); |
| |
| smMap.insert (std::make_pair (mask.fName, mask)); |
| |
| } |
| |
| } |
| |
| // Next, walk through all tables in the RGBTables tag and figure out which |
| // ones are relevant (have a matching SemanticMask label, or have no label |
| // which means a background table). |
| |
| int32 debugIndex = 0; |
| |
| #if !qDebugMaskedRGBTableRender |
| (void) debugIndex; |
| #endif |
| |
| for (const auto &table : tables.Tables ()) |
| { |
| |
| DNG_REQUIRE (table, "bad table"); |
| |
| const auto &name = table->SemanticName (); |
| |
| if (name.IsEmpty ()) |
| { |
| |
| DNG_REQUIRE (fBackgroundTable == nullptr, |
| "already have a background table"); |
| |
| fBackgroundTable = table; |
| |
| if (fUseSequentialMethod) |
| { |
| |
| dng_semantic_mask emptyMask; |
| |
| fMaskedTables.push_back |
| (std::make_pair (table, emptyMask)); |
| |
| } |
| |
| #if qDebugMaskedRGBTableRender |
| |
| printf ("table %d will be treated as background table\n", |
| debugIndex); |
| |
| #endif |
| |
| } |
| |
| else |
| { |
| |
| // Check if we have a corresponding semantic mask. |
| |
| auto iter = smMap.find (name); |
| |
| if (iter != smMap.end ()) |
| { |
| |
| // Found it. Add it to the list. |
| |
| fMaskedTables.push_back |
| (std::make_pair (table, iter->second)); |
| |
| #if qDebugMaskedRGBTableRender |
| |
| printf ("table index %d -> semantic name '%s' found\n", |
| debugIndex, |
| name.Get ()); |
| |
| #endif |
| |
| } |
| |
| #if qDebugMaskedRGBTableRender |
| |
| else |
| { |
| |
| printf ("table index %d -> semantic name '%s' not found among " |
| "negative's list of semantic masks -- ignoring", |
| debugIndex, |
| name.Get ()); |
| |
| } |
| |
| #endif |
| |
| } |
| |
| ++debugIndex; |
| |
| } |
| |
| // Find the background table index. |
| |
| fBackgroundTableIndex = uint32 (fMaskedTables.size ()); |
| |
| if (fUseSequentialMethod) |
| { |
| |
| for (size_t i = 0; i < fMaskedTables.size (); i++) |
| { |
| |
| const auto &semanticMask = fMaskedTables [i].second; |
| |
| const_dng_image_sptr baseMask = semanticMask.fMask; |
| |
| // Empty base mask indicates a background table. |
| |
| if (!baseMask) |
| { |
| |
| fBackgroundTableIndex = (uint32) i; |
| |
| break; |
| |
| } |
| |
| } |
| |
| DNG_REQUIRE ((!fBackgroundTable) == |
| (fBackgroundTableIndex == fMaskedTables.size ()), |
| "inconsistent background table info for sequential"); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_table_render_data::PrepareRGBtoRGBTableData (dng_host &host) |
| { |
| |
| fMaskedTableData.clear (); |
| |
| fMaskedTableData.reserve (fMaskedTables.size ()); |
| |
| for (const auto &x : fMaskedTables) |
| { |
| |
| dng_rgb_to_rgb_table_data_sptr ptr |
| (host.Make_dng_rgb_to_rgb_table_data (x.first->Table ())); |
| |
| fMaskedTableData.push_back (ptr); |
| |
| } |
| |
| if (fBackgroundTable) |
| { |
| |
| fBackgroundTableData.Reset |
| (host.Make_dng_rgb_to_rgb_table_data (fBackgroundTable->Table ())); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| #if qDNGValidate |
| |
| void dng_masked_rgb_table::Dump (uint32 indent) const |
| { |
| |
| const uint32 kIndentBufLen = 32; |
| |
| indent = Min_uint32 (indent, kIndentBufLen - 1); |
| |
| char indentBuf [kIndentBufLen]; |
| |
| snprintf (indentBuf, |
| kIndentBufLen, |
| "%*s", |
| indent, |
| ""); |
| |
| printf ("%s%-20s: %s\n", |
| indentBuf, |
| "SemanticName", |
| fTableSemanticName.Get ()); |
| |
| printf ("%s%-20s: %u\n", |
| indentBuf, |
| "Divisions", |
| fTable.Divisions ()); |
| |
| const char *pixelTypeStrings [] = { "u8", "u16", "fp32" }; |
| |
| printf ("%s%-20s: %u (%s)\n", |
| indentBuf, |
| "PixelType", |
| fPixelType, |
| pixelTypeStrings [Pin_uint32 (0, fPixelType, 2)]); |
| |
| const char *gammaEncodingStrings [] = |
| { |
| "linear", |
| "sRGB", |
| "1.8", |
| "2.2", |
| "Rec. 2020" |
| }; |
| |
| uint32 gammaTagValue = GammaEnumToRGBTablesTagValue (fTable.Gamma ()); |
| |
| printf ("%s%-20s: %u (%s)\n", |
| indentBuf, |
| "GammaEncoding", |
| gammaTagValue, |
| gammaEncodingStrings [gammaTagValue]); |
| |
| const char *primariesStrings [] = |
| { |
| "sRGB", |
| "Adobe RGB", |
| "Display P3", |
| "Rec. 2020", |
| "ProPhoto" |
| }; |
| |
| uint32 primariesTagValue = |
| ColorPrimariesEnumToRGBTablesTagValue (fTable.Primaries ()); |
| |
| printf ("%s%-20s: %u (%s)\n", |
| indentBuf, |
| "ColorPrimaries", |
| primariesTagValue, |
| primariesStrings [primariesTagValue]); |
| |
| const char *gamutStrings [] = |
| { |
| "clip", |
| "extend", |
| }; |
| |
| uint32 gamutTagValue = |
| GamutExtensionEnumToRGBTablesTagValue (fTable.Gamut ()); |
| |
| printf ("%s%-20s: %u (%s)\n", |
| indentBuf, |
| "GamutExtension", |
| gamutTagValue, |
| gamutStrings [gamutTagValue]); |
| |
| const uint32 divs = fTable.Divisions (); |
| |
| const uint16 *samples = fTable.Samples (); |
| |
| bool doPrint = true; |
| |
| uint32 linesPrinted = 0; |
| |
| const uint8 *storedPtr = nullptr; |
| |
| if (fStoredData.get ()) |
| { |
| storedPtr = fStoredData->Buffer_uint8 (); |
| } |
| |
| for (uint32 rIndex = 0; rIndex < divs && doPrint; rIndex++) |
| { |
| |
| for (uint32 gIndex = 0; gIndex < divs && doPrint; gIndex++) |
| { |
| |
| for (uint32 bIndex = 0; bIndex < divs; bIndex++) |
| { |
| |
| if (linesPrinted >= gDumpLineLimit) |
| { |
| doPrint = false; |
| break; |
| } |
| |
| if (!storedPtr || (fPixelType == 1)) |
| { |
| |
| printf (" table [%3u] [%3u] [%3u] = %6u %6u %6u\n", |
| rIndex, |
| gIndex, |
| bIndex, |
| (unsigned) samples [0], |
| (unsigned) samples [1], |
| (unsigned) samples [2]); |
| |
| } |
| |
| else if (fPixelType == 0) |
| { |
| |
| printf (" table [%3u] [%3u] [%3u] = %6u %6u %6u (stored as %4u %4u %4u)\n", |
| rIndex, |
| gIndex, |
| bIndex, |
| (unsigned) samples [0], |
| (unsigned) samples [1], |
| (unsigned) samples [2], |
| (unsigned) storedPtr [0], |
| (unsigned) storedPtr [1], |
| (unsigned) storedPtr [2]); |
| |
| storedPtr += 3; |
| |
| } |
| |
| else // fPixelType == 2 |
| { |
| |
| const real32 *sPtr32 = (const real32 *) storedPtr; |
| |
| printf (" table [%3u] [%3u] [%3u] = %6u %6u %6u (stored as %6.4f %6.4f %6.4f)\n", |
| rIndex, |
| gIndex, |
| bIndex, |
| (unsigned) samples [0], |
| (unsigned) samples [1], |
| (unsigned) samples [2], |
| sPtr32 [0], |
| sPtr32 [1], |
| sPtr32 [2]); |
| |
| storedPtr += (3 * 4); |
| |
| } |
| |
| samples += 4; |
| |
| ++linesPrinted; |
| |
| } // b |
| |
| } // g |
| |
| } // r |
| |
| if (linesPrinted < divs * divs * divs) |
| { |
| |
| printf ("%s... %u entries omitted ...\n", |
| indentBuf, |
| (divs * divs * divs) - linesPrinted); |
| |
| } |
| |
| } |
| |
| #endif // qDNGValidate |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_masked_rgb_tables::dng_masked_rgb_tables () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_masked_rgb_tables::dng_masked_rgb_tables |
| (const std::vector<std::shared_ptr<dng_masked_rgb_table> > &tables, |
| composite_method compositeMethod) |
| |
| : fCompositeMethod (compositeMethod) |
| |
| , fTables (tables) |
| |
| { |
| |
| Validate (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_masked_rgb_tables::IsNOP () const |
| { |
| |
| if (fTables.empty ()) |
| { |
| return true; |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_tables::Validate () const |
| { |
| |
| // Check number of tables. |
| |
| if (fTables.size () > kMaxTables) |
| { |
| ThrowBadFormat ("Too many tables in RGBTables"); |
| } |
| |
| // Check each table and keep track of # of tables with empty semantic |
| // names. |
| |
| uint32 numEmptyNames = 0; |
| |
| for (const auto &t : fTables) |
| { |
| |
| DNG_REQUIRE (t, "Invalid table pointer in RGBTables"); |
| |
| t->Validate (); |
| |
| if (t->SemanticName ().IsEmpty ()) |
| { |
| ++numEmptyNames; |
| } |
| |
| } |
| |
| // There can be at most 1 table with an empty semantic name (i.e., at most |
| // one background table). |
| |
| if (numEmptyNames > 1) |
| { |
| |
| ThrowBadFormat ("Only one table in RGBTables can " |
| "have empty semantic name"); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_tables::AddDigest (dng_md5_printer_stream &printer) const |
| { |
| |
| // Header. |
| |
| printer.ProcessPtr ("dng_masked_rgb_tables", 21); |
| |
| // Number of tables. |
| |
| const uint32 numTables = (uint32) fTables.size (); |
| |
| printer.Put_uint32 (numTables); |
| |
| // Process each table. |
| |
| for (const auto &t : fTables) |
| { |
| t->AddDigest (printer); |
| } |
| |
| // Composite method. |
| |
| printer.Put_uint32 (uint32 (fCompositeMethod)); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_masked_rgb_tables::PutStream (dng_stream &stream) const |
| { |
| |
| // Write the number of tables. |
| |
| stream.Put_uint32 ((uint32) fTables.size ()); |
| |
| // Write the composite method. |
| |
| stream.Put_uint32 ((uint32) fCompositeMethod); |
| |
| // Then write each table. |
| |
| for (const auto &t : fTables) |
| { |
| t->PutStream (stream); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_masked_rgb_tables * dng_masked_rgb_tables::GetStream (dng_host &host, |
| dng_stream &stream, |
| const bool isDraft) |
| { |
| |
| // Read number of tables. |
| |
| uint32 numTables = stream.Get_uint32 (); |
| |
| // A zero value is valid, which simply means the tag is empty and is a |
| // NOP. Return nullptr in this case. |
| |
| if (numTables == 0) |
| { |
| return nullptr; |
| } |
| |
| if (numTables > kMaxTables) |
| { |
| ThrowBadFormat ("RGBTables: numTables too large"); |
| } |
| |
| // In earlier DNG 1.6 drafts, there was no CompositeMethod field. The only |
| // supported method was Weighted Sum. |
| |
| composite_method method = kWeightedSum; |
| |
| // In the final DNG 1.6 revision, there is a CompositeMethod field. |
| |
| if (!isDraft) |
| { |
| |
| method = (composite_method) stream.Get_uint32 (); |
| |
| if (method != kWeightedSum && |
| method != kSequential) |
| { |
| ThrowBadFormat ("RGBTables: invalid composite method"); |
| } |
| |
| } |
| |
| // Read each table. |
| |
| std::vector<std::shared_ptr<dng_masked_rgb_table> > tables; |
| |
| tables.resize (numTables); |
| |
| for (auto &t : tables) |
| { |
| |
| t.reset (new dng_masked_rgb_table); |
| |
| t->GetStream (host, stream); |
| |
| } |
| |
| // Done reading all the tables. |
| |
| return new dng_masked_rgb_tables (tables, |
| method); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| #if qDNGValidate |
| |
| void dng_masked_rgb_tables::Dump () const |
| { |
| |
| printf ("Number of tables: %u\n", (unsigned) fTables.size ()); |
| |
| printf ("CompositeMethod: %s\n", |
| UseWeightedSumMethod () ? "Weighted Sum" |
| : "Sequential"); |
| |
| for (size_t i = 0; i < fTables.size (); i++) |
| { |
| |
| printf ("Table %u:\n", (unsigned) i); |
| |
| fTables [i]->Dump (4); |
| |
| } |
| |
| } |
| |
| #endif // qDNGValidate |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| class dng_rgb_to_rgb_1d_function : public dng_1d_function |
| { |
| |
| private: |
| |
| const dng_rgb_table &fTable; |
| |
| uint32 fPlane; |
| |
| public: |
| |
| dng_rgb_to_rgb_1d_function (const dng_rgb_table &table, |
| uint32 plane) |
| |
| : fTable (table) |
| , fPlane (plane) |
| |
| { |
| |
| DNG_ASSERT (fTable.Dimensions () == 1, "1D table expected"); |
| |
| } |
| |
| virtual real64 Evaluate (real64 x) const |
| { |
| |
| uint32 divisions = fTable.Divisions (); |
| |
| real64 scaled = x * (real64) (divisions - 1); |
| |
| int32 index = Pin_int32 (0, |
| (int32) scaled, |
| divisions - 2); |
| |
| real64 fract = scaled - (real64) index; |
| |
| const uint16 *table = fTable.Samples () + (index * 4) + fPlane; |
| |
| real64 y = ((1.0 - fract) * (real64) table [0] + |
| ( fract) * (real64) table [4]) * (1.0 / 65535.0); |
| |
| return x + fTable.Amount () * (y - x); |
| |
| } |
| |
| }; |
| |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| dng_rgb_to_rgb_table_data::dng_rgb_to_rgb_table_data (dng_host &host, |
| const dng_rgb_table &table) |
| |
| : fTable (table) |
| |
| , fNeedMatrix (false) |
| |
| , fEncodeMatrix () |
| , fDecodeMatrix () |
| |
| , fEncodeTable () |
| , fDecodeTable () |
| |
| { |
| |
| // Find encode/decode matrix, if any. |
| |
| { |
| |
| const dng_color_space *space = NULL; |
| |
| switch (table.Primaries ()) |
| { |
| |
| case dng_rgb_table::primaries_sRGB: |
| { |
| space = &dng_space_sRGB::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::primaries_Adobe: |
| { |
| space = &dng_space_AdobeRGB::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::primaries_ProPhoto: |
| { |
| break; |
| } |
| |
| case dng_rgb_table::primaries_P3: |
| { |
| space = &dng_space_DisplayP3::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::primaries_Rec2020: |
| { |
| space = &dng_space_Rec2020::Get (); |
| break; |
| } |
| |
| default: |
| { |
| DNG_REPORT ("Unknown RGB table primaries"); |
| } |
| |
| } |
| |
| fNeedMatrix = (space != NULL); |
| |
| if (fNeedMatrix) |
| { |
| |
| fEncodeMatrix = space->MatrixFromPCS () * |
| dng_space_ProPhoto::Get ().MatrixToPCS (); |
| |
| fDecodeMatrix = dng_space_ProPhoto::Get ().MatrixFromPCS () * |
| space->MatrixToPCS (); |
| |
| } |
| |
| } |
| |
| // Find encode/decode gamma tables, if any. |
| |
| { |
| |
| const dng_1d_function *gamma = NULL; |
| |
| switch (table.Gamma ()) |
| { |
| |
| case dng_rgb_table::gamma_Linear: |
| { |
| break; |
| } |
| |
| case dng_rgb_table::gamma_sRGB: |
| { |
| gamma = &dng_function_GammaEncode_sRGB::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::gamma_1_8: |
| { |
| gamma = &dng_function_GammaEncode_1_8::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::gamma_2_2: |
| { |
| gamma = &dng_function_GammaEncode_2_2::Get (); |
| break; |
| } |
| |
| case dng_rgb_table::gamma_Rec2020: |
| { |
| gamma = &dng_function_GammaEncode_Rec709::Get (); |
| break; |
| } |
| |
| default: |
| { |
| DNG_REPORT ("Unknown RGB table gamma"); |
| } |
| |
| } |
| |
| if (fTable.Dimensions () == 1) |
| { |
| |
| for (uint32 plane = 0; plane < 3; plane++) |
| { |
| |
| fTable1D [plane].Reset (new dng_1d_table); |
| |
| dng_rgb_to_rgb_1d_function mapPlane (fTable, |
| plane); |
| |
| if (gamma == NULL) |
| { |
| |
| fTable1D [plane]->Initialize (host.Allocator (), |
| mapPlane, |
| false); |
| |
| } |
| |
| else |
| { |
| |
| dng_1d_inverse inverse (*gamma); |
| |
| dng_1d_concatenate firstPart (*gamma, |
| mapPlane); |
| |
| dng_1d_concatenate combined (firstPart, |
| inverse); |
| |
| fTable1D [plane]->Initialize (host.Allocator (), |
| combined, |
| false); |
| |
| } |
| |
| } |
| |
| } |
| |
| else if (gamma != NULL) |
| { |
| |
| fEncodeTable.Reset (new dng_1d_table); |
| fDecodeTable.Reset (new dng_1d_table); |
| |
| fEncodeTable->Initialize (host.Allocator (), |
| *gamma, |
| false); |
| |
| dng_1d_inverse inverse (*gamma); |
| |
| fDecodeTable->Initialize (host.Allocator (), |
| inverse, |
| false); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_rgb_to_rgb_table_data::~dng_rgb_to_rgb_table_data () |
| { |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_rgb_to_rgb_table_data::Process_32 (dng_pixel_buffer &buffer, |
| dng_pixel_buffer *optMaskBuffer, |
| uint32 optMaskPlane, |
| const dng_rect &dstArea, |
| uint32 bufferStartPlane, |
| const bool needOverrange) |
| { |
| |
| uint32 p0 = bufferStartPlane; |
| uint32 p1 = bufferStartPlane + 1; |
| uint32 p2 = bufferStartPlane + 2; |
| |
| const real32 *mPtr = nullptr; |
| |
| int32 mRowStep = 0; |
| |
| if (optMaskBuffer) |
| { |
| |
| mPtr = optMaskBuffer->ConstPixel_real32 (dstArea.t, |
| dstArea.l, |
| optMaskPlane); |
| |
| mRowStep = optMaskBuffer->RowStep (); |
| |
| } |
| |
| if (fTable.Dimensions () == 3) |
| { |
| |
| DoRGBtoRGBTable3D (buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p0), |
| buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p1), |
| buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p2), |
| mPtr, |
| dstArea.H (), |
| dstArea.W (), |
| buffer.RowStep (), |
| mRowStep, |
| fTable.Divisions (), |
| fTable.Samples (), |
| (real32) fTable.Amount (), |
| (uint32) fTable.Gamut (), |
| fNeedMatrix ? &fEncodeMatrix : NULL, |
| fNeedMatrix ? &fDecodeMatrix : NULL, |
| fEncodeTable.Get (), |
| fDecodeTable.Get (), |
| needOverrange); |
| |
| } |
| |
| else |
| { |
| |
| DoRGBtoRGBTable1D (buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p0), |
| buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p1), |
| buffer.DirtyPixel_real32 (dstArea.t, dstArea.l, p2), |
| mPtr, |
| dstArea.H (), |
| dstArea.W (), |
| buffer.RowStep (), |
| mRowStep, |
| *fTable1D [0], |
| *fTable1D [1], |
| *fTable1D [2], |
| (uint32) fTable.Gamut (), |
| fNeedMatrix ? &fEncodeMatrix : NULL, |
| fNeedMatrix ? &fDecodeMatrix : NULL, |
| needOverrange); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_rgb_to_rgb_table_data::AddDigest (dng_md5_printer_stream &printer) const |
| { |
| |
| { |
| |
| const dng_fingerprint tableFingerPrint = fTable.Fingerprint (); |
| |
| printer.Process (tableFingerPrint); |
| |
| } |
| |
| if (fNeedMatrix) |
| { |
| |
| for (uint32 i = 0; i < 3; i++) |
| { |
| |
| #if 1 |
| |
| static_assert (sizeof (fEncodeMatrix [i] [0]) == 8, "matrix coeffs not 8 bytes"); |
| |
| printer.Put_swap8 (fEncodeMatrix [i], uint32 (3 * sizeof (fEncodeMatrix [i] [0]))); |
| printer.Put_swap8 (fDecodeMatrix [i], uint32 (3 * sizeof (fEncodeMatrix [i] [0]))); |
| |
| #else |
| |
| printer.Process (fEncodeMatrix [i], 3 * sizeof (fEncodeMatrix [i] [0])); |
| printer.Process (fDecodeMatrix [i], 3 * sizeof (fEncodeMatrix [i] [0])); |
| |
| #endif |
| |
| } |
| |
| } |
| |
| if (fEncodeTable.Get () && fDecodeTable.Get ()) |
| { |
| |
| #if 1 |
| |
| static_assert (sizeof (fEncodeTable->Table () [0]) == 4, "table data not 4 bytes"); |
| |
| printer.Put_swap4 (fEncodeTable->Table (), |
| uint32 ((2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0]))); |
| |
| printer.Put_swap4 (fDecodeTable->Table (), |
| uint32 ((2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0]))); |
| |
| #else |
| |
| printer.Process (fEncodeTable->Table (), |
| (2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0])); |
| |
| printer.Process (fDecodeTable->Table (), |
| (2 + fEncodeTable->Count ()) * sizeof (fEncodeTable->Table () [0])); |
| |
| #endif |
| |
| } |
| |
| if (fTable.Dimensions () != 3) |
| { |
| |
| for (uint32 i = 0; i < 3; i++) |
| { |
| |
| static_assert (sizeof (fTable1D [i]->Table () [0]) == 4, "table data not 4 bytes"); |
| |
| #if 1 |
| |
| printer.Put_swap4 (fTable1D [i]->Table (), |
| uint32 ((2 + fTable1D [i]->Count ()) * sizeof (fTable1D [i]->Table () [0]))); |
| |
| #else |
| |
| printer.Process (fTable1D [i]->Table (), |
| (2 + fTable1D [i]->Count ()) * sizeof (fTable1D [i]->Table () [0])); |
| |
| #endif |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| /*****************************************************************************/ |
| |
| #if qDNGUseXMP |
| |
| /*****************************************************************************/ |
| |
| class MoveBigTablesToDictionaryContext |
| { |
| |
| public: |
| |
| dng_xmp &fXMP; |
| |
| dng_big_table_dictionary &fDictionary; |
| |
| public: |
| |
| MoveBigTablesToDictionaryContext (dng_xmp &xmp, |
| dng_big_table_dictionary &dictionary) |
| |
| : fXMP (xmp) |
| , fDictionary (dictionary) |
| |
| { |
| |
| } |
| |
| }; |
| |
| /*****************************************************************************/ |
| |
| static bool MoveBigTablesToDictionaryCallback (const char *ns, |
| const char *path, |
| void *callbackData) |
| { |
| |
| if (path == NULL || path [0] == 0) |
| { |
| return true; |
| } |
| |
| dng_string s; |
| |
| s.Set (path); |
| |
| // Check if ends with Table_ and 32 characters. |
| |
| if (s.Length () < 38 || |
| strncmp (s.Get () + s.Length () - 38, |
| "Table_", |
| 6) != 0) |
| { |
| return true; |
| } |
| |
| // Make sure there is either nothing or a namespace before. |
| |
| if (s.Length () >= 39 && |
| s.Get () [s.Length () - 39] != ':') |
| { |
| return true; |
| } |
| |
| // See if the last 32 characters are a valid fingerprint. |
| |
| char fingerString [32]; |
| |
| memcpy (fingerString, |
| s.Get () + s.Length () - 32, |
| 32); |
| |
| dng_fingerprint fingerprint; |
| |
| if (!fingerprint.FromUtf8HexString (fingerString)) |
| { |
| return true; |
| } |
| |
| if (!fingerprint.IsValid ()) |
| { |
| return true; |
| } |
| |
| MoveBigTablesToDictionaryContext *context = |
| (MoveBigTablesToDictionaryContext *) callbackData; |
| |
| if (!context->fDictionary.HasTable (fingerprint)) |
| { |
| |
| dng_string tableString; |
| |
| if (context->fXMP.GetString (ns, path, tableString)) |
| { |
| |
| AutoPtr<dng_memory_block> dBlock; |
| |
| uint32 dCount = 0; |
| |
| dng_big_table::ASCIItoBinary (context->fXMP.Allocator (), |
| tableString.Get (), |
| tableString.Length (), |
| dBlock, |
| dCount); |
| |
| if (dCount) |
| { |
| |
| dng_ref_counted_block bigTableBlock (dCount); |
| |
| memcpy (bigTableBlock.Buffer (), |
| dBlock->Buffer (), |
| dCount); |
| |
| context->fDictionary.AddTable (fingerprint, |
| bigTableBlock); |
| |
| } |
| |
| } |
| |
| } |
| |
| // Now remove the big table string from the xmp. |
| |
| context->fXMP.Remove (ns, path); |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void MoveBigTablesToDictionary (dng_xmp &xmp, |
| dng_big_table_dictionary &dictionary) |
| { |
| |
| MoveBigTablesToDictionaryContext context (xmp, dictionary); |
| |
| xmp.IteratePaths (MoveBigTablesToDictionaryCallback, |
| (void *) &context, |
| XMP_NS_CRS, |
| nullptr, |
| true); |
| |
| xmp.IteratePaths (MoveBigTablesToDictionaryCallback, |
| (void *) &context, |
| XMP_NS_CRSS, |
| nullptr, |
| true); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void DualParseXMP (dng_host &host, |
| dng_xmp &xmp, |
| dng_big_table_dictionary &dictionary, |
| const void *blockData, |
| uint32 blockSize) |
| { |
| |
| // Null pad XMP text so we can use string searching functions. |
| |
| AutoPtr<dng_memory_block> tempBlock (host.Allocate (blockSize + 1)); |
| |
| memcpy (tempBlock->Buffer (), |
| blockData, |
| blockSize); |
| |
| tempBlock->Buffer_uint8 () [blockSize] = 0; |
| |
| char *blockString = tempBlock->Buffer_char (); |
| |
| // Assume default namespace short names. Since this is just |
| // a performance optimization, don't worry about the rare case |
| // of non-default namespace short names. |
| |
| dng_string ns_crs ("crs:"); |
| dng_string ns_crss ("crss:"); |
| |
| // Search for big tables in XMP using fast search. |
| |
| struct TableEntry |
| { |
| |
| // Fingerprint of found big table. |
| |
| dng_fingerprint fFingerprint; |
| |
| // Offset and byte count of big table data. |
| |
| size_t fDataOffset; |
| size_t fDataBytes; |
| |
| // Offset and byte count of entire big table tag, including wrapper. |
| |
| size_t fWrapperOffset; |
| size_t fWrapperBytes; |
| |
| }; |
| |
| std::vector<TableEntry> entries; |
| |
| { |
| |
| size_t offset = 0; |
| |
| while (const char *search = strstr (blockString + offset, "Table_")) |
| { |
| |
| TableEntry entry; |
| |
| // Is the search result followed by a valid hex encoded fingerprint? |
| |
| if (search + 6 + 32 > blockString + blockSize) |
| { |
| break; // Too near end of xmp block to even fit fingerprint |
| } |
| |
| char fingerString [33]; |
| |
| memcpy (fingerString, |
| search + 6, |
| 32); |
| |
| fingerString [32] = 0; |
| |
| if (!entry.fFingerprint.FromUtf8HexString (fingerString)) |
| { |
| |
| // We did not find a table, so just start searching again just |
| // after the "Table_" string. |
| |
| offset = (search - blockString) + 6; |
| |
| continue; // Not followed by valid fingerprint |
| |
| } |
| |
| // Look for big tables in both the XMP_NS_CRS and XMP_NS_CRSS |
| // namespaces. |
| |
| bool foundTable = false; |
| |
| for (uint32 nsIndex = 0; nsIndex < 2; nsIndex++) |
| { |
| |
| dng_string ns = (nsIndex == 0 ? ns_crs : ns_crss); |
| |
| int32 nsLength = ns.Length (); |
| |
| // Look for compact mode tables. |
| // Example: |
| // crs:Table_fingerprint="data" |
| |
| if (search >= blockString + nsLength + 1 && |
| memcmp (search - nsLength, ns.Get (), nsLength) == 0 && |
| (search [-nsLength - 1] == ' ' || |
| search [-nsLength - 1] == '\t' || |
| search [-nsLength - 1] == '\n' || |
| search [-nsLength - 1] == '\r') && |
| search + 6 + 32 + 2 < blockString + blockSize && |
| memcmp (search + 6 + 32, "=\"", 2) == 0) |
| { |
| |
| const char *dataStart = search + 6 + 32 + 2; |
| |
| // Find termination |
| |
| const char *endSearch = strstr (dataStart, "\""); |
| |
| if (endSearch) |
| { |
| |
| entry.fDataOffset = dataStart - blockString; |
| entry.fDataBytes = endSearch - dataStart; |
| |
| entry.fWrapperOffset = entry.fDataOffset - 2 - 32 - 6 - nsLength; |
| entry.fWrapperBytes = entry.fDataBytes + nsLength + 6 + 32 + 2 + 1; |
| |
| foundTable = true; |
| |
| break; |
| |
| } |
| |
| } |
| |
| // Look for non-compact mode tables. |
| // Example: |
| // <crs:Table_fingerprint>data</crs:Table_fingerprint> |
| |
| if (search >= blockString + nsLength + 1 && |
| memcmp (search - nsLength, ns.Get (), nsLength) == 0 && |
| search [-nsLength - 1] == '<' && |
| search + 6 + 32 + 1 < blockString + blockSize && |
| search [6 + 32] == '>') |
| { |
| |
| const char *dataStart = search + 6 + 32 + 1; |
| |
| dng_string endString ("</"); |
| |
| endString.Append (ns.Get ()); |
| endString.Append ("Table_"); |
| endString.Append (fingerString); |
| endString.Append (">"); |
| |
| // Find termination |
| |
| const char *endSearch = strstr (dataStart, endString.Get ()); |
| |
| if (endSearch) |
| { |
| |
| entry.fDataOffset = dataStart - blockString; |
| entry.fDataBytes = endSearch - dataStart; |
| |
| entry.fWrapperOffset = entry.fDataOffset - 2 - 32 - 6 - nsLength; |
| entry.fWrapperBytes = entry.fDataBytes + (nsLength + 6 + 32 + 2) * 2 + 1; |
| |
| foundTable = true; |
| |
| break; |
| |
| } |
| |
| } |
| |
| } |
| |
| // We found a table, start search again after this table's wrapper. |
| |
| if (foundTable) |
| { |
| |
| entries.push_back (entry); |
| |
| offset = entry.fWrapperOffset + |
| entry.fWrapperBytes; |
| |
| } |
| |
| // If we did not find a table, just start searching again just |
| // after the "Table_" string. |
| |
| else |
| { |
| |
| offset = (search - blockString) + 6; |
| |
| } |
| |
| } |
| |
| } |
| |
| // Did we find any tables in our scan? |
| |
| if (entries.size ()) |
| { |
| |
| // Add any new tables to dictionary, and remove from xmp that will be passed |
| // to parser. |
| |
| size_t srcOffset = 0; |
| size_t dstOffset = 0; |
| |
| for (size_t index = 0; index < entries.size (); index++) |
| { |
| |
| if (!dictionary.HasTable (entries [index].fFingerprint)) |
| { |
| |
| AutoPtr<dng_memory_block> dBlock; |
| |
| uint32 dCount = 0; |
| |
| dng_big_table::ASCIItoBinary (host.Allocator (), |
| ((const char *) blockData) + entries [index].fDataOffset, |
| (uint32) entries [index].fDataBytes, |
| dBlock, |
| dCount); |
| |
| if (dCount) |
| { |
| |
| dng_ref_counted_block bigTableBlock (dCount); |
| |
| memcpy (bigTableBlock.Buffer (), |
| dBlock->Buffer (), |
| dCount); |
| |
| dictionary.AddTable (entries [index].fFingerprint, |
| bigTableBlock); |
| |
| } |
| |
| } |
| |
| size_t copyBytes = entries [index].fWrapperOffset - srcOffset; |
| |
| memcpy (blockString + dstOffset, |
| ((const char *) blockData) + srcOffset, |
| copyBytes); |
| |
| dstOffset += copyBytes; |
| |
| srcOffset = entries [index].fWrapperOffset + |
| entries [index].fWrapperBytes; |
| |
| } |
| |
| // Copy bytes after last block. |
| |
| memcpy (blockString + dstOffset, |
| ((const char *) blockData) + srcOffset, |
| blockSize - srcOffset); |
| |
| dstOffset += blockSize - srcOffset; |
| |
| // Parse the remaining XMP with the big table blocks removed. |
| |
| xmp.Parse (host, |
| blockString, |
| (uint32) dstOffset); |
| |
| } |
| |
| // No tables founds, just parse the original XMP data. |
| |
| else |
| { |
| |
| xmp.Parse (host, |
| blockData, |
| blockSize); |
| |
| } |
| |
| // Slow path, in case fast parser missed something. |
| |
| MoveBigTablesToDictionary (xmp, dictionary); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| #endif // qDNGUseXMP |
| |
| /*****************************************************************************/ |