You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1951 lines
44 KiB
C++

/*****************************************************************************/
// Copyright 2015-2019 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_memory_stream.h"
#include "dng_mutex.h"
#include "dng_stream.h"
#include "dng_xmp.h"
#include "zlib.h"
#include <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;
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 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 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;
/*****************************************************************************/
void dng_big_table_cache_flush ()
{
gLookTableCache.FlushRecentlyUsed ();
gRGBTableCache.FlushRecentlyUsed ();
}
/*****************************************************************************/
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;
}
/*****************************************************************************/
void dng_big_table::RecomputeFingerprint ()
{
dng_big_table_cache::Decrement (fCache, fFingerprint);
fFingerprint.Clear ();
if (IsValid ())
{
{
dng_md5_printer_stream stream;
stream.SetLittleEndian ();
PutStream (stream, true);
fFingerprint = stream.Result ();
}
// 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 (const uint8 *compressedData,
uint32 compressedSize,
dng_memory_allocator &allocator)
{
// Decompress the data.
AutoPtr<dng_memory_block> block3;
{
if (compressedSize < 5)
{
return false;
}
// Uncompressed size is stored in first four bytes of decoded data,
// little endian order.
uint32 uncompressedSize = (((uint32) compressedData [0]) ) +
(((uint32) compressedData [1]) << 8) +
(((uint32) compressedData [2]) << 16) +
(((uint32) compressedData [3]) << 24);
block3.Reset (allocator.Allocate (uncompressedSize));
uLongf destLen = uncompressedSize;
int zResult = ::uncompress (block3->Buffer_uint8 (),
&destLen,
compressedData + 4,
compressedSize - 4);
if (zResult != Z_OK)
{
return false;
}
}
// Now read in the table data from the uncompressed stream.
{
dng_stream stream (block3->Buffer (),
block3->LogicalSize ());
stream.SetLittleEndian ();
GetStream (stream);
block3.Reset ();
}
// Force recomputation of fingerprint.
RecomputeFingerprint ();
return true;
}
/*****************************************************************************/
bool dng_big_table::DecodeFromString (const dng_string &block1,
dng_memory_allocator &allocator)
{
// Decode the text to binary.
AutoPtr<dng_memory_block> block2;
uint32 compressedSize = 0;
{
// This binary to text encoding is very similar to the Z85
// encoding, but the exact charactor 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
};
uint32 encodedSize = block1.Length ();
uint32 maxCompressedSize = (encodedSize + 4) / 5 * 4;
block2.Reset (allocator.Allocate (maxCompressedSize));
uint32 phase = 0;
uint32 value;
uint8 *dPtr = block2->Buffer_uint8 ();
for (const char *sPtr = block1.Get (); *sPtr; sPtr++)
{
uint8 e = (uint8) *sPtr;
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;
compressedSize += 4;
phase = 0;
}
}
if (phase > 3)
{
dPtr [2] = (uint8) (value >> 16);
compressedSize++;
}
if (phase > 2)
{
dPtr [1] = (uint8) (value >> 8);
compressedSize++;
}
if (phase > 1)
{
dPtr [0] = (uint8) (value);
compressedSize++;
}
}
return DecodeFromBinary (block2->Buffer_uint8 (),
compressedSize,
allocator);
}
/*****************************************************************************/
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));
}
// 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 charactor 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;
}
/*****************************************************************************/
bool dng_big_table::ReadTableFromXMP (const dng_xmp &xmp,
const char *ns,
const dng_fingerprint &fingerprint)
{
// Read in the table data.
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;
}
bool ok = DecodeFromString (block1,
xmp.Allocator ());
block1.Clear ();
// Validate fingerprint match.
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_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.
if (ReadTableFromXMP (xmp, ns, fingerprint))
{
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 ());
}
/*****************************************************************************/
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 */)
{
return false;
}
/*****************************************************************************/
void dng_big_table_storage::MissingTable (const dng_fingerprint & /* fingerprint */)
{
}
/*****************************************************************************/
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 ();
}
/*****************************************************************************/
void dng_look_table::GetStream (dng_stream &stream)
{
table_data data;
if (stream.Get_uint32 () != btt_LookTable)
{
ThrowBadFormat ("Not a look table");
}
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 ();
fData = data;
}
/*****************************************************************************/
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);
}
}
/*****************************************************************************/
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 ();
}
/*****************************************************************************/
void dng_rgb_table::GetStream (dng_stream &stream)
{
table_data data;
if (stream.Get_uint32 () != btt_RGBTable)
{
ThrowBadFormat ("Not a RGB table");
}
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 ();
fData = data;
}
/*****************************************************************************/
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);
}
/*****************************************************************************/