blob: 079a08fe4387386351293dcefe2ef8c8b8119cf7 [file] [log] [blame] [edit]
/*****************************************************************************/
// Copyright 2006-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_utils.h"
#include "dng_area_task.h"
#include "dng_assertions.h"
#include "dng_bottlenecks.h"
#include "dng_flags.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_image_writer.h"
#include "dng_memory_stream.h"
#include "dng_mutex.h"
#include "dng_point.h"
#include "dng_rect.h"
#include "dng_simd_type.h"
#include "dng_tag_codes.h"
#include "dng_tag_values.h"
#include "dng_tile_iterator.h"
#if qMacOS
#include <CoreServices/CoreServices.h>
#endif
#if qiPhone || qMacOS
// these provide timers
#include <mach/mach.h>
#include <mach/mach_time.h>
#endif
#if qiPhone || qLinux
#include <signal.h> // for raise
#endif
#if qWinOS
#include <windows.h>
#else
#include <sys/time.h>
#include <stdarg.h> // for va_start/va_end
#endif
#if defined(__EMSCRIPTEN__)
#include "emscripten.h"
#endif
#include <atomic>
/*****************************************************************************/
#if qDNGDebug
/*****************************************************************************/
#if qMacOS
#if qARM
#define DNG_DEBUG_BREAK do { } while (0)
#else
#define DNG_DEBUG_BREAK __asm__ volatile ("int3")
#endif
#elif qiPhone
#if qiPhoneSimulator
// simulator is either on Intel or arm64
#if qARM
#define DNG_DEBUG_BREAK do { } while (0)
#else
#define DNG_DEBUG_BREAK __asm__ volatile ("int3")
#endif
#else
// You'll be one level deeper in __kill. Works on Linux, Android too.
#define DNG_DEBUG_BREAK raise(SIGTRAP)
#endif
#elif qWinOS
// DebugBreak has to be emulated on WinRT
#define DNG_DEBUG_BREAK DebugBreak()
#elif qAndroid
#define DNG_DEBUG_BREAK raise(SIGTRAP)
#elif qLinux
#define DNG_DEBUG_BREAK raise(SIGTRAP)
#else
#define DNG_DEBUG_BREAK
#endif
/*****************************************************************************/
#endif // qDNGDebug
/*****************************************************************************/
#if qWinOS
void dng_outputdebugstring (const char *s,
const char *nl)
{
static const bool sDebuggerPresent (IsDebuggerPresent () != 0);
if (sDebuggerPresent)
{
OutputDebugStringA (s);
if (nl && nl [0])
{
OutputDebugStringA (nl);
}
}
}
#endif
/*****************************************************************************/
#if defined(__EMSCRIPTEN__)
void dng_emscripten_log (int emLogType,
const char *s)
{
#if qDNGDebug
emLogType |= EM_LOG_CONSOLE;
#endif
emscripten_log (emLogType,"%s", s);
}
#endif
/*****************************************************************************/
void dng_show_message (const char *s)
{
// only append a newline if there isn't already one
const char* nl = "\n";
if (s[0] && (s[strlen(s)-1] == '\n'))
nl = "";
#if qDNGPrintMessages
// display the message
if (gPrintAsserts)
fprintf (stderr, "%s%s", s, nl);
#elif qiPhone || qAndroid || qLinux || qWeb
if (gPrintAsserts)
fprintf (stderr, "%s%s", s, nl);
#if qDNGDebug
// iOS doesn't print a message to the console like DebugStr and MessageBox do, so we have to do both
// You'll have to advance the program counter manually past this statement
if (gBreakOnAsserts)
DNG_DEBUG_BREAK;
#endif // qDNGDebug
#elif qMacOS
if (gBreakOnAsserts)
{
// truncate the to 255 chars
char ss [256];
uint32 len = (uint32) strlen (s);
if (len > 255)
len = 255;
strncpy (&(ss [1]), s, len );
ss [0] = (unsigned char) len;
DebugStr ((unsigned char *) ss);
}
else if (gPrintAsserts)
{
// For macOS have non-breaking assert emit to stdout
// rather than stderr so that message will appear
// in the Xcode console window.
//fprintf(stderr, "%s%s", s, nl);
fprintf (stdout, "%s%s", s, nl);
}
#elif qWinOS
// display a dialog
// This is not thread safe. Multiple message boxes can be launched.
// Should also be launched in its own thread so main msg queue isn't thrown off.
if (gBreakOnAsserts)
{
MessageBoxA (NULL, (LPSTR) s, NULL, MB_OK);
}
else if (gPrintAsserts)
{
fprintf (stderr, "%s%s", s, nl);
// Emit assert message to MSVS Output window when debugging.
dng_outputdebugstring (s, nl);
}
#endif
}
/*****************************************************************************/
void dng_show_message_f (const char *fmt, ... )
{
char buffer [2048];
va_list ap;
va_start (ap, fmt);
vsnprintf (buffer, sizeof (buffer), fmt, ap);
va_end (ap);
dng_show_message (buffer);
}
/*****************************************************************************/
uint32 ComputeBufferSize (uint32 pixelType,
const dng_point &tileSize,
uint32 numPlanes,
PaddingType paddingType)
{
// Convert tile size to uint32.
if (tileSize.h < 0 || tileSize.v < 0)
{
ThrowMemoryFull ("Negative tile size");
}
const uint32 tileSizeH = static_cast<uint32> (tileSize.h);
const uint32 tileSizeV = static_cast<uint32> (tileSize.v);
const uint32 pixelSize = TagTypeSize (pixelType);
// Add padding to width if necessary.
uint32 paddedWidth = tileSizeH;
if (paddingType == padSIMDBytes)
{
if (!RoundUpForPixelSize (paddedWidth,
pixelSize,
&paddedWidth))
{
ThrowOverflow ("Arithmetic overflow computing buffer size");
}
}
// Compute buffer size.
uint32 bufferSize;
if (!SafeUint32Mult (paddedWidth, tileSizeV, &bufferSize) ||
!SafeUint32Mult (bufferSize, pixelSize, &bufferSize) ||
!SafeUint32Mult (bufferSize, numPlanes, &bufferSize))
{
ThrowOverflow ("Arithmetic overflow computing buffer size");
}
return bufferSize;
}
/*****************************************************************************/
real64 TickTimeInSeconds ()
{
#if qWinOS
// One might think it prudent to cache the frequency here, however
// low-power CPU modes can, and do, change the value returned.
// Thus the frequencey needs to be retrieved each time.
// Note that the frequency changing can cause the return
// result to jump backwards, which is why the TickCountInSeconds
// (below) also exists.
// Just plug in laptop when doing timings to minimize this.
// QPC/QPH is a slow call compared to rtdsc.
// but QPC/QPF is not tied to speed step, it's the northbridge timer.
// caching the invFrequency also avoids a costly divide
static real64 freqMultiplier = 0.0;
if (freqMultiplier == 0.0)
{
LARGE_INTEGER freq;
QueryPerformanceFrequency (&freq);
freqMultiplier = 1.0 / (real64) freq.QuadPart;
}
LARGE_INTEGER cycles;
QueryPerformanceCounter (&cycles);
return (real64) cycles.QuadPart * freqMultiplier;
#elif qiPhone || qMacOS
// cache frequency of high-perf timer
static real64 freqMultiplier = 0.0;
if (freqMultiplier == 0.0)
{
mach_timebase_info_data_t freq;
mach_timebase_info(&freq);
// converts from nanos to micros
// numer = 125, denom = 3 * 1000
freqMultiplier = ((real64)freq.numer / (real64)freq.denom) * 1.0e-9;
}
return mach_absolute_time() * freqMultiplier;
#elif qAndroid || qLinux || qWeb
//this is a fast timer to nanos
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return now.tv_sec + (real64)now.tv_nsec * 1.0e-9;
#else
// Perhaps a better call exists. (e.g. avoid adjtime effects)
struct timeval tv;
gettimeofday (&tv, NULL);
return tv.tv_sec + (real64)tv.tv_usec * 1.0e-6;
#endif
}
/*****************************************************************************/
real64 TickCountInSeconds ()
{
return TickTimeInSeconds ();
}
/*****************************************************************************/
static std::atomic_int sTimerLevel (0);
/*****************************************************************************/
void DNGIncrementTimerLevel ()
{
// This isn't thread coherent, multiple threads can create/destroy cr_timer
// causing the tabbing to be invalid. Imagecore disables this.
if (!gImagecore)
{
sTimerLevel++;
}
}
/*****************************************************************************/
int32 DNGDecrementTimerLevel ()
{
if (gImagecore)
{
return 0;
}
else
{
return (int32) (--sTimerLevel);
}
}
/*****************************************************************************/
dng_timer::dng_timer (const char *message)
: fMessage (message )
, fStartTime (TickTimeInSeconds ())
{
DNGIncrementTimerLevel ();
}
/*****************************************************************************/
dng_timer::~dng_timer ()
{
uint32 level = Pin_int32 (0, DNGDecrementTimerLevel (), 10);
if (!gDNGShowTimers)
return;
real64 totalTime = TickTimeInSeconds () - fStartTime;
#if defined(qCRLogging) && qCRLogging && defined(cr_logi)
if (gImagecore)
{
// Imagecore force includes cr_log and overrides DNG to go to its logging under a mutex.
// don't use indenting or fprintf to stderr, want these buffered
cr_logi("timer", "%s: %0.3f sec\n", fMessage, totalTime);
return;
}
#endif
fprintf (stderr, "%*s%s: %0.3f sec\n", level*2, "", fMessage, totalTime);
}
/*****************************************************************************/
real64 MaxSquaredDistancePointToRect (const dng_point_real64 &point,
const dng_rect_real64 &rect)
{
real64 distSqr = DistanceSquared (point,
rect.TL ());
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.BL ()));
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.BR ()));
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.TR ()));
return distSqr;
}
/*****************************************************************************/
real64 MaxDistancePointToRect (const dng_point_real64 &point,
const dng_rect_real64 &rect)
{
return sqrt (MaxSquaredDistancePointToRect (point,
rect));
}
/*****************************************************************************/
dng_dither::dng_dither ()
: fNoiseBuffer ()
{
const uint32 kSeed = 1;
fNoiseBuffer.Allocate (kRNGSize2D * sizeof (uint16));
uint16 *buffer = fNoiseBuffer.Buffer_uint16 ();
uint32 seed = kSeed;
for (uint32 i = 0; i < kRNGSize2D; i++)
{
// The correct math for 16 to 8-bit dither would be:
//
// y = (x * 255 + r) / 65535; (0 <= r <= 65534)
//
// The bottlnecks are using a faster approximation of
// this math (using a power of two for the division):
//
// y = (x * 255 + r) / 65536; (255 <= r <= 65535)
//
// To insure that all exact 8 bit values in 16 bit space
// round trip exactly to the same 8-bit, we need to limit
// r values to the range 255 to 65535.
//
// This results in the dither effect being slightly
// imperfect, but correct round-tripping of 8-bit values
// is far more important.
uint16 value;
do
{
seed = DNG_Random (seed);
value = (uint16) seed;
}
while (value < 255);
buffer [i] = value;
}
}
/******************************************************************************/
const dng_dither & dng_dither::Get ()
{
static dng_dither dither;
return dither;
}
/*****************************************************************************/
void HistogramArea (dng_host & /* host */,
const dng_image &image,
const dng_rect &area,
uint32 *hist,
uint32 maxValue,
uint32 plane)
{
DNG_ASSERT (image.PixelType () == ttShort, "Unsupported pixel type");
DoZeroBytes (hist, (maxValue + 1) * (uint32) sizeof (uint32));
dng_rect tile;
dng_tile_iterator iter (image, area);
while (iter.GetOneTile (tile))
{
dng_const_tile_buffer buffer (image, tile);
const void *sPtr = buffer.ConstPixel (tile.t,
tile.l,
plane);
uint32 count0 = 1;
uint32 count1 = tile.H ();
uint32 count2 = tile.W ();
int32 step0 = 0;
int32 step1 = buffer.fRowStep;
int32 step2 = buffer.fColStep;
OptimizeOrder (sPtr,
buffer.fPixelSize,
count0,
count1,
count2,
step0,
step1,
step2);
DNG_ASSERT (count0 == 1, "OptimizeOrder logic error");
const uint16 *s1 = (const uint16 *) sPtr;
for (uint32 row = 0; row < count1; row++)
{
if (maxValue == 0x0FFFF && step2 == 1)
{
for (uint32 col = 0; col < count2; col++)
{
uint32 x = s1 [col];
hist [x] ++;
}
}
else
{
const uint16 *s2 = s1;
for (uint32 col = 0; col < count2; col++)
{
uint32 x = s2 [0];
if (x <= maxValue)
{
hist [x] ++;
}
s2 += step2;
}
}
s1 += step1;
}
}
}
/*****************************************************************************/
template <SIMDType simd>
class dng_limit_float_depth_task: public dng_area_task
{
private:
const dng_image &fSrcImage;
dng_image &fDstImage;
uint32 fBitDepth;
real32 fScale;
public:
dng_limit_float_depth_task (const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale);
virtual dng_rect RepeatingTile1 () const
{
return fSrcImage.RepeatingTile ();
}
virtual dng_rect RepeatingTile2 () const
{
return fDstImage.RepeatingTile ();
}
virtual void Process (uint32 threadIndex,
const dng_rect &tile,
dng_abort_sniffer *sniffer);
};
/*****************************************************************************/
template <SIMDType simd>
dng_limit_float_depth_task<simd>::dng_limit_float_depth_task
(const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale)
: dng_area_task ("dng_limit_float_depth_task")
, fSrcImage (srcImage)
, fDstImage (dstImage)
, fBitDepth (bitDepth)
, fScale (scale)
{
}
/*****************************************************************************/
template <SIMDType simd>
#ifdef __INTEL_LLVM_COMPILER
__attribute__((SET_CPU_FEATURE(simd)))
#endif // __INTEL_LLVM_COMPILER
void dng_limit_float_depth_task<simd>::Process (uint32 /* threadIndex */,
const dng_rect &tile,
dng_abort_sniffer * /* sniffer */)
{
INTEL_COMPILER_NEEDED_NOTE
#ifdef __INTEL_COMPILER
SET_CPU_FEATURE(simd);
#endif // __INTEL_COMPILER
dng_const_tile_buffer srcBuffer (fSrcImage, tile);
dng_dirty_tile_buffer dstBuffer (fDstImage, tile);
uint32 count0 = tile.H ();
uint32 count1 = tile.W ();
uint32 count2 = fDstImage.Planes ();
int32 sStep0 = srcBuffer.fRowStep;
int32 sStep1 = srcBuffer.fColStep;
int32 sStep2 = srcBuffer.fPlaneStep;
int32 dStep0 = dstBuffer.fRowStep;
int32 dStep1 = dstBuffer.fColStep;
int32 dStep2 = dstBuffer.fPlaneStep;
const void *sPtr = srcBuffer.ConstPixel (tile.t,
tile.l,
0);
void *dPtr = dstBuffer.DirtyPixel (tile.t,
tile.l,
0);
OptimizeOrder (sPtr,
dPtr,
srcBuffer.fPixelSize,
dstBuffer.fPixelSize,
count0,
count1,
count2,
sStep0,
sStep1,
sStep2,
dStep0,
dStep1,
dStep2);
const real32 *sPtr0 = (const real32 *) sPtr;
real32 *dPtr0 = ( real32 *) dPtr;
real32 scale = fScale;
bool limit16 = (fBitDepth == 16);
bool limit24 = (fBitDepth == 24);
for (uint32 index0 = 0; index0 < count0; index0++)
{
const real32 *sPtr1 = sPtr0;
real32 *dPtr1 = dPtr0;
for (uint32 index1 = 0; index1 < count1; index1++)
{
// If the scale is a NOP, and the data is packed solid, we can just do memory
// copy.
if (scale == 1.0f && sStep2 == 1 && dStep2 == 1)
{
if (dPtr1 != sPtr1) // srcImage != dstImage
{
memcpy (dPtr1, sPtr1, count2 * (uint32) sizeof (real32));
}
}
else
{
const real32 *sPtr2 = sPtr1;
real32 *dPtr2 = dPtr1;
INTEL_PRAGMA_SIMD_ASSERT_VECLEN_FLOAT(simd)
for (uint32 index2 = 0; index2 < count2; index2++)
{
real32 x = sPtr2 [0];
x *= scale;
dPtr2 [0] = x;
sPtr2 += sStep2;
dPtr2 += dStep2;
}
}
// The data is now in the destination buffer.
if (limit16)
{
//start by using intrinsic __m256__mm256_cvtph_ps_(__m128i_a)
//once the intrinsic is written, merge this branch with previous one
uint32 *dPtr2 = (uint32 *) dPtr1;
INTEL_PRAGMA_SIMD_ASSERT_VECLEN_INT32(simd)
for (uint32 index2 = 0; index2 < count2; index2++)
{
uint32 x = dPtr2 [0];
uint16 y = DNG_FloatToHalf (x);
x = DNG_HalfToFloat (y);
dPtr2 [0] = x;
dPtr2 += dStep2;
}
}
else if (limit24)
{
uint32 *dPtr2 = (uint32 *) dPtr1;
for (uint32 index2 = 0; index2 < count2; index2++)
{
uint32 x = dPtr2 [0];
uint8 temp [3];
DNG_FloatToFP24 (x, temp);
x = DNG_FP24ToFloat (temp);
dPtr2 [0] = x;
dPtr2 += dStep2;
}
}
sPtr1 += sStep1;
dPtr1 += dStep1;
}
sPtr0 += sStep0;
dPtr0 += dStep0;
}
}
/******************************************************************************/
template <SIMDType simd>
void LimitFloatBitDepth (dng_host &host,
const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale)
{
DNG_ASSERT (srcImage.PixelType () == ttFloat, "Floating point image expected");
DNG_ASSERT (dstImage.PixelType () == ttFloat, "Floating point image expected");
dng_limit_float_depth_task<simd> task (srcImage,
dstImage,
bitDepth,
scale);
host.PerformAreaTask (task, dstImage.Bounds ());
}
/*****************************************************************************/
template
void LimitFloatBitDepth<Scalar> (dng_host &host,
const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale);
/*****************************************************************************/
#if qDNGIntelCompiler
template
void LimitFloatBitDepth<AVX2> (dng_host &host,
const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale);
#endif // qDNGIntelCompiler
/*****************************************************************************/
void LimitFloatBitDepth (dng_host &host,
const dng_image &srcImage,
dng_image &dstImage,
uint32 bitDepth,
real32 scale)
{
// Kludge: Turning this off for now because the AVX2 path produces
// slightly different results from the Scalar routine causing a mis-match
// in raw digest values when building HDR merge result negatives which
// causes the client to display a "file appears to be damaged" warning.
// -bury 11/13/2017
#if (qDNGIntelCompiler && qDNGExperimental && 0)
if (gDNGMaxSIMD >= AVX2)
{
LimitFloatBitDepth<AVX2> (host,
srcImage,
dstImage,
bitDepth,
scale);
}
else
#endif // qDNGIntelCompiler && qDNGExperimental
{
LimitFloatBitDepth<Scalar> (host,
srcImage,
dstImage,
bitDepth,
scale);
}
}
/*****************************************************************************/
uint32 MinBackwardVersionForCompression (uint32 compression)
{
if (compression == ccLossyJPEG)
return dngVersion_1_4_0_0;
if (compression == ccJXL)
return dngVersion_1_7_0_0;
return dngVersion_1_1_0_0;
}
/*****************************************************************************/
tiff_tag * dng_image_sequence_info::MakeTag (dng_memory_allocator &allocator) const
{
dng_memory_stream stream (allocator);
TempBigEndian tempEndian (stream);
// Write Sequence ID.
if (fSequenceID.NotEmpty ())
stream.Put (fSequenceID.Get (),
fSequenceID.Length ());
stream.PutZeros (1);
// Write Sequence Type.
if (fSequenceType.NotEmpty ())
stream.Put (fSequenceType.Get (),
fSequenceType.Length ());
stream.PutZeros (1);
// Write frame info.
if (fFrameInfo.NotEmpty ())
stream.Put (fFrameInfo.Get (),
fFrameInfo.Length ());
stream.PutZeros (1);
// Write index, count, and final.
stream.Put_uint32 (fIndex);
stream.Put_uint32 (fCount);
stream.Put_uint8 (fIsFinal);
stream.SetReadPosition (0);
const_dng_memory_block_sptr block (stream.AsMemoryBlock (allocator));
AutoPtr<tag_owned_data_ptr> tag
(new tag_owned_data_ptr (tcImageSequenceInfo,
ttUndefined,
block->LogicalSize (),
block));
return tag.Release ();
}
/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
bool dng_image_stats::IsValidForPlaneCount (uint32 planeCount) const
{
DNG_REQUIRE (planeCount > 0, "Invalid plane count");
if (fWeightedAverage.size () >= 2)
return false;
if (fWeights.size () != 0 &&
fWeights.size () != size_t (planeCount))
return false;
if (fColorAverage.size () != 0 &&
fColorAverage.size () != size_t (planeCount))
return false;
// Check weighted samples.
if (!fWeightedSamples.empty ())
{
// Check too many samples.
if (fWeightedSamples.size () > kMaxSamples)
return false;
// Check non-increasing order.
real32 fPrev = fWeightedSamples.front ().fFrac;
for (size_t i = 1; i < fWeightedSamples.size (); i++)
{
real32 fCurrent = fWeightedSamples [i].fFrac;
if (fCurrent <= fPrev)
return false;
fPrev = fCurrent;
}
}
// Check color samples.
if (!fColorSamples.empty ())
{
// Check too many samples.
if (fColorSamples.size () > kMaxSamples)
return false;
// Check non-increasing order and invalid plane count.
real32 fPrev = fColorSamples.front ().fFrac;
for (size_t i = 1; i < fColorSamples.size (); i++)
{
const auto &sample = fColorSamples [i];
if (sample.fFrac <= fPrev)
return false;
fPrev = sample.fFrac;
// Also check plane count.
if (sample.fValues.size () != size_t (planeCount))
return false;
}
}
// Looks ok.
return true;
}
/*****************************************************************************/
uint32 dng_image_stats::TagCount () const
{
uint32 count = 0;
count += (!fWeightedAverage.empty () ? 1 : 0);
count += (!fWeightedSamples.empty () ? 1 : 0);
count += (!fWeights .empty () ? 1 : 0);
count += (!fColorAverage .empty () ? 1 : 0);
count += (!fColorSamples .empty () ? 1 : 0);
return count;
}
/*****************************************************************************/
static void Put (dng_stream &stream,
uint32 tagCode,
const std::vector<real32> &values)
{
if (!values.empty ())
{
// Sanity check on values size.
DNG_REQUIRE (values.size () <= 16,
"values vector too large");
// Child tag code.
stream.Put_uint32 (tagCode);
// Byte length of child tag data.
stream.Put_uint32 (uint32 (4 * values.size ()));
// Child tag data.
for (const auto x : values)
stream.Put_real32 (x);
}
}
/*****************************************************************************/
static void Put (dng_stream &stream,
uint32 tagCode,
const std::vector<dng_image_stats::weighted_sample> &samples)
{
if (!samples.empty ())
{
// Sanity check on samples size.
DNG_REQUIRE (samples.size () <= dng_image_stats::kMaxSamples,
"samples vector too large");
// Child tag code.
stream.Put_uint32 (tagCode);
// Byte length of child tag data.
stream.Put_uint32 (uint32 (4 + 8 * samples.size ()));
// Child tag data.
// Write number of samples.
stream.Put_uint32 (uint32 (samples.size ()));
// Write data for each sample.
for (const auto &sample : samples)
{
stream.Put_real32 (sample.fFrac);
stream.Put_real32 (sample.fValue);
}
}
}
/*****************************************************************************/
static void Put (dng_stream &stream,
uint32 tagCode,
const std::vector<dng_image_stats::color_sample> &samples)
{
if (!samples.empty ())
{
// Sanity check on samples size.
DNG_REQUIRE (samples.size () <= dng_image_stats::kMaxSamples,
"samples vector too large");
// Child tag code.
stream.Put_uint32 (tagCode);
// Byte length of child tag data.
uint32 bytes = 4;
for (const auto &sample : samples)
bytes += (4 + 4 * uint32 (sample.fValues.size ()));
stream.Put_uint32 (bytes);
// Child tag data.
// Write number of samples.
stream.Put_uint32 (uint32 (samples.size ()));
// Write data for each sample.
for (const auto &sample : samples)
{
stream.Put_real32 (sample.fFrac);
for (const auto &x : sample.fValues)
stream.Put_real32 (x);
}
}
}
/*****************************************************************************/
tiff_tag * dng_image_stats::MakeTag (dng_memory_allocator &allocator) const
{
dng_memory_stream stream (allocator);
// Tag data is big-endian byte order.
TempBigEndian tempEndian (stream);
// Write the number of tags.
uint32 count = TagCount ();
stream.Put_uint32 (count);
// Write child tag table.
Put (stream, kTag_WeightedAverage , fWeightedAverage);
Put (stream, kTag_WeightedSamples , fWeightedSamples);
Put (stream, kTag_Weights , fWeights);
Put (stream, kTag_ColorAverage , fColorAverage);
Put (stream, kTag_ColorSamples , fColorSamples);
// Make a tag from the data.
stream.SetReadPosition (0);
const_dng_memory_block_sptr block (stream.AsMemoryBlock (allocator));
AutoPtr<tag_owned_data_ptr> tag
(new tag_owned_data_ptr (tcImageStats,
ttUndefined,
block->LogicalSize (),
block));
return tag.Release ();
}
/*****************************************************************************/
bool dng_image_stats::operator== (const dng_image_stats &src) const
{
return (fWeightedAverage == src.fWeightedAverage &&
fWeightedSamples == src.fWeightedSamples &&
fWeights == src.fWeights &&
fColorAverage == src.fColorAverage &&
fColorSamples == src.fColorSamples);
}
/*****************************************************************************/
void dng_image_stats::Parse (dng_stream &stream)
{
// Tag data is big-endian byte order.
TempBigEndian tempEndian (stream);
// Read the number of tags.
uint32 count = stream.Get_uint32 ();
// There are only 5 possible tags, and no tag may be repeated. Therefore,
// more than 5 tags is an error.
if (count > 5)
ThrowBadFormat ("too many tags in dng_image_stats");
// Read each child tag.
for (uint32 i = 0; i < count; i++)
{
// Read child tag code.
uint32 childTagCode = stream.Get_uint32 ();
// Read byte length of child tag data.
uint32 length = stream.Get_uint32 ();
// Byte length must be positive.
if (length == 0)
ThrowBadFormat ("child tag byte length must be > 0");
// Byte length must be multiple of 4.
if ((length & 3) != 0)
ThrowBadFormat ("child tag byte length expected to be multiple of 4");
// Check maximum value of byte length.
constexpr uint32 kMaxBytes = 4 + kMaxSamples * 4 * (kMaxColorPlanes + 1);
if (length > kMaxBytes)
ThrowBadFormat ("child tag byte length too large");
// Read all floats.
std::vector<real32> *data = nullptr;
switch (childTagCode)
{
case kTag_WeightedAverage:
{
data = &fWeightedAverage;
break;
}
case kTag_Weights:
{
data = &fWeights;
break;
}
case kTag_ColorAverage:
{
data = &fColorAverage;
break;
}
default:
break;
}
if (data)
{
const uint32 numFloats = (length >> 2);
data->resize (numFloats);
for (uint32 c = 0; c < numFloats; c++)
(*data) [c] = stream.Get_real32 ();
}
else if (childTagCode == kTag_WeightedSamples)
{
const uint32 sampleCount = stream.Get_uint32 ();
// Check sample count requirements.
if (sampleCount == 0)
ThrowBadFormat ("too few samples for weighted samples");
if (sampleCount > kMaxSamples)
ThrowBadFormat ("too many samples for weighted samples");
// Check byte length.
if (4 + (8 * sampleCount) != length)
ThrowBadFormat ("mismatch byte length for weighted samples");
// Read the child tag data.
fWeightedSamples.resize (sampleCount);
for (auto &sample : fWeightedSamples)
{
sample.fFrac = stream.Get_real32 ();
sample.fValue = stream.Get_real32 ();
}
}
else if (childTagCode == kTag_ColorSamples)
{
const uint32 sampleCount = stream.Get_uint32 ();
// Check sample count requirements.
if (sampleCount == 0)
ThrowBadFormat ("too few samples for color samples");
if (sampleCount > kMaxSamples)
ThrowBadFormat ("too many samples for color samples");
// Infer plane count.
const uint32 planes = ((length - 4) / sampleCount / 4) - 1;
if (planes == 0)
ThrowBadFormat ("unexpected 0 plane count for color samples");
if (planes > kMaxColorPlanes)
ThrowBadFormat ("too large plane count for color samples");
if (4 + (sampleCount * 4 * (planes + 1)) != length)
ThrowBadFormat ("mismatched plane count for color samples");
// Read the child tag data.
fColorSamples.resize (sampleCount);
for (auto &sample : fColorSamples)
{
sample.fFrac = stream.Get_real32 ();
sample.fValues.resize (planes);
for (auto &value : sample.fValues)
value = stream.Get_real32 ();
}
}
else
{
ThrowBadFormat ("unsupported child tag code");
}
}
}
/*****************************************************************************/
#if qDNGValidate
/*****************************************************************************/
static void DumpTag (const char *name,
const std::vector<real32> &values)
{
if (!values.empty ())
{
printf (" %s: %.4f",
name,
values.front ());
for (size_t i = 1; i < values.size (); i++)
printf (", %.4f", values [i]);
printf ("\n");
}
}
/*****************************************************************************/
void dng_image_stats::Dump () const
{
printf ("ImageStats: %u child tag(s)\n", TagCount ());
DumpTag ("weights", fWeights);
DumpTag ("weighted average", fWeightedAverage);
DumpTag ("color average", fColorAverage);
if (!fWeightedSamples.empty ())
{
printf (" weighted samples:\n");
for (const auto &s : fWeightedSamples)
{
printf (" frac: %.6f, value: %.6lf\n",
s.fFrac,
s.fValue);
}
}
if (!fColorSamples.empty ())
{
printf (" color samples:\n");
for (const auto &s : fColorSamples)
{
printf (" frac: %.6f, values: ",
s.fFrac);
if (s.fValues.empty ())
continue;
printf ("%.6f", s.fValues.front ());
for (size_t i = 1; i < s.fValues.size (); i++)
printf (", %.6f", s.fValues [i]);
printf ("\n");
}
}
}
/*****************************************************************************/
#endif // qDNGValidate
/*****************************************************************************/