| /*****************************************************************************/ |
| // 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 |
| |
| /*****************************************************************************/ |