File indexing completed on 2025-01-19 03:55:18
0001 /*****************************************************************************/ 0002 // Copyright 2006-2019 Adobe Systems Incorporated 0003 // All Rights Reserved. 0004 // 0005 // NOTICE: Adobe permits you to use, modify, and distribute this file in 0006 // accordance with the terms of the Adobe license agreement accompanying it. 0007 /*****************************************************************************/ 0008 0009 #include "dng_utils.h" 0010 0011 #include "dng_area_task.h" 0012 #include "dng_assertions.h" 0013 #include "dng_bottlenecks.h" 0014 #include "dng_flags.h" 0015 #include "dng_globals.h" 0016 #include "dng_host.h" 0017 #include "dng_image.h" 0018 #include "dng_mutex.h" 0019 #include "dng_point.h" 0020 #include "dng_rect.h" 0021 #include "dng_simd_type.h" 0022 #include "dng_tile_iterator.h" 0023 0024 #if qMacOS 0025 #include <CoreServices/CoreServices.h> 0026 #endif 0027 0028 #if qiPhone || qMacOS 0029 // these provide timers 0030 #include <mach/mach.h> 0031 #include <mach/mach_time.h> 0032 #endif 0033 0034 #if qiPhone || qLinux 0035 #include <signal.h> // for raise 0036 #endif 0037 0038 #if qWinOS 0039 #include <windows.h> 0040 #else 0041 #include <sys/time.h> 0042 #include <stdarg.h> // for va_start/va_end 0043 #endif 0044 0045 #include <atomic> 0046 0047 /*****************************************************************************/ 0048 0049 #if qDNGDebug 0050 0051 /*****************************************************************************/ 0052 0053 #if qMacOS 0054 #define DNG_DEBUG_BREAK __asm__ volatile ("int3") 0055 #elif qiPhone 0056 #if qiPhoneSimulator 0057 // simulator is running on Intel 0058 #define DNG_DEBUG_BREAK __asm__ volatile ("int3") 0059 #else 0060 // You'll be one level deeper in __kill. Works on Linux, Android too. 0061 #define DNG_DEBUG_BREAK raise(SIGTRAP) 0062 #endif 0063 #elif qWinOS 0064 // DebugBreak has to be emulated on WinRT 0065 #define DNG_DEBUG_BREAK DebugBreak() 0066 #elif qAndroid 0067 #define DNG_DEBUG_BREAK raise(SIGTRAP) 0068 #elif qLinux 0069 #define DNG_DEBUG_BREAK raise(SIGTRAP) 0070 #else 0071 #define DNG_DEBUG_BREAK 0072 #endif 0073 0074 /*****************************************************************************/ 0075 0076 void dng_show_message (const char *s) 0077 { 0078 // only append a newline if there isn't already one 0079 const char* nl = "\n"; 0080 if (s[0] && (s[strlen(s)-1] == '\n')) 0081 nl = ""; 0082 0083 #if qDNGPrintMessages 0084 0085 // display the message 0086 if (gPrintAsserts) 0087 fprintf (stderr, "%s%s", s, nl); 0088 0089 #elif qiPhone || qAndroid || qLinux 0090 0091 if (gPrintAsserts) 0092 fprintf (stderr, "%s%s", s, nl); 0093 0094 // iOS doesn't print a message to the console like DebugStr and MessageBox do, so we have to do both 0095 // You'll have to advance the program counter manually past this statement 0096 if (gBreakOnAsserts) 0097 DNG_DEBUG_BREAK; 0098 0099 #elif qMacOS 0100 0101 if (gBreakOnAsserts) 0102 { 0103 // truncate the to 255 chars 0104 char ss [256]; 0105 0106 uint32 len = (uint32) strlen (s); 0107 if (len > 255) 0108 len = 255; 0109 strncpy (&(ss [1]), s, len ); 0110 ss [0] = (unsigned char) len; 0111 0112 DebugStr ((unsigned char *) ss); 0113 } 0114 else if (gPrintAsserts) 0115 { 0116 fprintf (stderr, "%s%s", s, nl); 0117 } 0118 0119 #elif qWinOS 0120 0121 // display a dialog 0122 // This is not thread safe. Multiple message boxes can be launched. 0123 // Should also be launched in its own thread so main msg queue isn't thrown off. 0124 if (gBreakOnAsserts) 0125 MessageBoxA (NULL, (LPSTR) s, NULL, MB_OK); 0126 else if (gPrintAsserts) 0127 fprintf (stderr, "%s%s", s, nl); 0128 0129 #endif 0130 0131 } 0132 0133 /*****************************************************************************/ 0134 0135 void dng_show_message_f (const char *fmt, ... ) 0136 { 0137 0138 char buffer [2048]; 0139 0140 va_list ap; 0141 va_start (ap, fmt); 0142 0143 vsnprintf (buffer, sizeof (buffer), fmt, ap); 0144 0145 va_end (ap); 0146 0147 dng_show_message (buffer); 0148 0149 } 0150 0151 /*****************************************************************************/ 0152 0153 #endif 0154 0155 /*****************************************************************************/ 0156 0157 uint32 ComputeBufferSize (uint32 pixelType, 0158 const dng_point &tileSize, 0159 uint32 numPlanes, 0160 PaddingType paddingType) 0161 { 0162 0163 // Convert tile size to uint32. 0164 0165 if (tileSize.h < 0 || tileSize.v < 0) 0166 { 0167 ThrowMemoryFull ("Negative tile size"); 0168 } 0169 0170 const uint32 tileSizeH = static_cast<uint32> (tileSize.h); 0171 const uint32 tileSizeV = static_cast<uint32> (tileSize.v); 0172 0173 const uint32 pixelSize = TagTypeSize (pixelType); 0174 0175 // Add padding to width if necessary. 0176 0177 uint32 paddedWidth = tileSizeH; 0178 0179 if (paddingType == padSIMDBytes) 0180 { 0181 0182 if (!RoundUpForPixelSize (paddedWidth, 0183 pixelSize, 0184 &paddedWidth)) 0185 { 0186 ThrowOverflow ("Arithmetic overflow computing buffer size"); 0187 } 0188 0189 } 0190 0191 // Compute buffer size. 0192 0193 uint32 bufferSize; 0194 0195 if (!SafeUint32Mult (paddedWidth, tileSizeV, &bufferSize) || 0196 !SafeUint32Mult (bufferSize, pixelSize, &bufferSize) || 0197 !SafeUint32Mult (bufferSize, numPlanes, &bufferSize)) 0198 { 0199 ThrowOverflow ("Arithmetic overflow computing buffer size"); 0200 } 0201 0202 return bufferSize; 0203 0204 } 0205 0206 /*****************************************************************************/ 0207 0208 real64 TickTimeInSeconds () 0209 { 0210 0211 #if qWinOS 0212 0213 // One might think it prudent to cache the frequency here, however 0214 // low-power CPU modes can, and do, change the value returned. 0215 // Thus the frequencey needs to be retrieved each time. 0216 0217 // Note that the frequency changing can cause the return 0218 // result to jump backwards, which is why the TickCountInSeconds 0219 // (below) also exists. 0220 0221 // Just plug in laptop when doing timings to minimize this. 0222 // QPC/QPH is a slow call compared to rtdsc. 0223 // but QPC/QPF is not tied to speed step, it's the northbridge timer. 0224 // caching the invFrequency also avoids a costly divide 0225 0226 static real64 freqMultiplier = 0.0; 0227 0228 if (freqMultiplier == 0.0) 0229 { 0230 0231 LARGE_INTEGER freq; 0232 0233 QueryPerformanceFrequency (&freq); 0234 0235 freqMultiplier = 1.0 / (real64) freq.QuadPart; 0236 0237 } 0238 0239 LARGE_INTEGER cycles; 0240 0241 QueryPerformanceCounter (&cycles); 0242 0243 return (real64) cycles.QuadPart * freqMultiplier; 0244 0245 #elif qiPhone || qMacOS 0246 0247 // cache frequency of high-perf timer 0248 static real64 freqMultiplier = 0.0; 0249 if (freqMultiplier == 0.0) 0250 { 0251 0252 mach_timebase_info_data_t freq; 0253 mach_timebase_info(&freq); 0254 0255 // converts from nanos to micros 0256 // numer = 125, denom = 3 * 1000 0257 freqMultiplier = ((real64)freq.numer / (real64)freq.denom) * 1.0e-9; 0258 0259 } 0260 0261 return mach_absolute_time() * freqMultiplier; 0262 0263 #elif qAndroid || qLinux 0264 0265 //this is a fast timer to nanos 0266 struct timespec now; 0267 clock_gettime(CLOCK_MONOTONIC, &now); 0268 return now.tv_sec + (real64)now.tv_nsec * 1.0e-9; 0269 0270 #else 0271 0272 // Perhaps a better call exists. (e.g. avoid adjtime effects) 0273 0274 struct timeval tv; 0275 0276 gettimeofday (&tv, NULL); 0277 0278 return tv.tv_sec + (real64)tv.tv_usec * 1.0e-6; 0279 0280 #endif 0281 0282 } 0283 0284 /*****************************************************************************/ 0285 0286 real64 TickCountInSeconds () 0287 { 0288 0289 return TickTimeInSeconds (); 0290 0291 } 0292 0293 /*****************************************************************************/ 0294 0295 static std::atomic_int sTimerLevel (0); 0296 0297 /*****************************************************************************/ 0298 0299 void DNGIncrementTimerLevel () 0300 { 0301 0302 // This isn't thread coherent, multiple threads can create/destroy cr_timer 0303 // causing the tabbing to be invalid. Imagecore disables this. 0304 0305 if (!gImagecore) 0306 { 0307 0308 sTimerLevel++; 0309 0310 } 0311 0312 } 0313 0314 /*****************************************************************************/ 0315 0316 int32 DNGDecrementTimerLevel () 0317 { 0318 0319 if (gImagecore) 0320 { 0321 0322 return 0; 0323 0324 } 0325 0326 else 0327 { 0328 0329 return (int32) (--sTimerLevel); 0330 0331 } 0332 0333 } 0334 0335 /*****************************************************************************/ 0336 0337 dng_timer::dng_timer (const char *message) 0338 0339 : fMessage (message ) 0340 , fStartTime (TickTimeInSeconds ()) 0341 0342 { 0343 0344 DNGIncrementTimerLevel (); 0345 0346 } 0347 0348 /*****************************************************************************/ 0349 0350 dng_timer::~dng_timer () 0351 { 0352 0353 uint32 level = Pin_int32 (0, DNGDecrementTimerLevel (), 10); 0354 0355 if (!gDNGShowTimers) 0356 return; 0357 0358 real64 totalTime = TickTimeInSeconds () - fStartTime; 0359 0360 #if defined(qCRLogging) && qCRLogging && defined(cr_logi) 0361 0362 if (gImagecore) 0363 { 0364 // Imagecore force includes cr_log and overrides DNG to go to its logging under a mutex. 0365 // don't use indenting or fprintf to stderr, want these buffered 0366 cr_logi("timer", "%s: %0.3f sec\n", fMessage, totalTime); 0367 return; 0368 } 0369 0370 #endif 0371 0372 fprintf (stderr, "%*s%s: %0.3f sec\n", level*2, "", fMessage, totalTime); 0373 0374 } 0375 0376 /*****************************************************************************/ 0377 0378 real64 MaxSquaredDistancePointToRect (const dng_point_real64 &point, 0379 const dng_rect_real64 &rect) 0380 { 0381 0382 real64 distSqr = DistanceSquared (point, 0383 rect.TL ()); 0384 0385 distSqr = Max_real64 (distSqr, 0386 DistanceSquared (point, 0387 rect.BL ())); 0388 0389 distSqr = Max_real64 (distSqr, 0390 DistanceSquared (point, 0391 rect.BR ())); 0392 0393 distSqr = Max_real64 (distSqr, 0394 DistanceSquared (point, 0395 rect.TR ())); 0396 0397 return distSqr; 0398 0399 } 0400 0401 /*****************************************************************************/ 0402 0403 real64 MaxDistancePointToRect (const dng_point_real64 &point, 0404 const dng_rect_real64 &rect) 0405 { 0406 0407 return sqrt (MaxSquaredDistancePointToRect (point, 0408 rect)); 0409 0410 } 0411 0412 /*****************************************************************************/ 0413 0414 dng_dither::dng_dither () 0415 0416 : fNoiseBuffer () 0417 0418 { 0419 0420 const uint32 kSeed = 1; 0421 0422 fNoiseBuffer.Allocate (kRNGSize2D * sizeof (uint16)); 0423 0424 uint16 *buffer = fNoiseBuffer.Buffer_uint16 (); 0425 0426 uint32 seed = kSeed; 0427 0428 for (uint32 i = 0; i < kRNGSize2D; i++) 0429 { 0430 0431 // The correct math for 16 to 8-bit dither would be: 0432 // 0433 // y = (x * 255 + r) / 65535; (0 <= r <= 65534) 0434 // 0435 // The bottlnecks are using a faster approximation of 0436 // this math (using a power of two for the division): 0437 // 0438 // y = (x * 255 + r) / 65536; (255 <= r <= 65535) 0439 // 0440 // To insure that all exact 8 bit values in 16 bit space 0441 // round trip exactly to the same 8-bit, we need to limit 0442 // r values to the range 255 to 65535. 0443 // 0444 // This results in the dither effect being slightly 0445 // imperfect, but correct round-tripping of 8-bit values 0446 // is far more important. 0447 0448 uint16 value; 0449 0450 do 0451 { 0452 0453 seed = DNG_Random (seed); 0454 0455 value = (uint16) seed; 0456 0457 } 0458 while (value < 255); 0459 0460 buffer [i] = value; 0461 0462 } 0463 0464 } 0465 0466 /******************************************************************************/ 0467 0468 const dng_dither & dng_dither::Get () 0469 { 0470 0471 static dng_dither dither; 0472 0473 return dither; 0474 0475 } 0476 0477 /*****************************************************************************/ 0478 0479 void HistogramArea (dng_host & /* host */, 0480 const dng_image &image, 0481 const dng_rect &area, 0482 uint32 *hist, 0483 uint32 maxValue, 0484 uint32 plane) 0485 { 0486 0487 DNG_ASSERT (image.PixelType () == ttShort, "Unsupported pixel type"); 0488 0489 DoZeroBytes (hist, (maxValue + 1) * (uint32) sizeof (uint32)); 0490 0491 dng_rect tile; 0492 0493 dng_tile_iterator iter (image, area); 0494 0495 while (iter.GetOneTile (tile)) 0496 { 0497 0498 dng_const_tile_buffer buffer (image, tile); 0499 0500 const void *sPtr = buffer.ConstPixel (tile.t, 0501 tile.l, 0502 plane); 0503 0504 uint32 count0 = 1; 0505 uint32 count1 = tile.H (); 0506 uint32 count2 = tile.W (); 0507 0508 int32 step0 = 0; 0509 int32 step1 = buffer.fRowStep; 0510 int32 step2 = buffer.fColStep; 0511 0512 OptimizeOrder (sPtr, 0513 buffer.fPixelSize, 0514 count0, 0515 count1, 0516 count2, 0517 step0, 0518 step1, 0519 step2); 0520 0521 DNG_ASSERT (count0 == 1, "OptimizeOrder logic error"); 0522 0523 const uint16 *s1 = (const uint16 *) sPtr; 0524 0525 for (uint32 row = 0; row < count1; row++) 0526 { 0527 0528 if (maxValue == 0x0FFFF && step2 == 1) 0529 { 0530 0531 for (uint32 col = 0; col < count2; col++) 0532 { 0533 0534 uint32 x = s1 [col]; 0535 0536 hist [x] ++; 0537 0538 } 0539 0540 } 0541 0542 else 0543 { 0544 0545 const uint16 *s2 = s1; 0546 0547 for (uint32 col = 0; col < count2; col++) 0548 { 0549 0550 uint32 x = s2 [0]; 0551 0552 if (x <= maxValue) 0553 { 0554 0555 hist [x] ++; 0556 0557 } 0558 0559 s2 += step2; 0560 0561 } 0562 0563 } 0564 0565 s1 += step1; 0566 0567 } 0568 0569 } 0570 0571 } 0572 0573 /*****************************************************************************/ 0574 0575 template <SIMDType simd> 0576 class dng_limit_float_depth_task: public dng_area_task 0577 { 0578 0579 private: 0580 0581 const dng_image &fSrcImage; 0582 0583 dng_image &fDstImage; 0584 0585 uint32 fBitDepth; 0586 0587 real32 fScale; 0588 0589 public: 0590 0591 dng_limit_float_depth_task (const dng_image &srcImage, 0592 dng_image &dstImage, 0593 uint32 bitDepth, 0594 real32 scale); 0595 0596 virtual dng_rect RepeatingTile1 () const 0597 { 0598 return fSrcImage.RepeatingTile (); 0599 } 0600 0601 virtual dng_rect RepeatingTile2 () const 0602 { 0603 return fDstImage.RepeatingTile (); 0604 } 0605 0606 virtual void Process (uint32 threadIndex, 0607 const dng_rect &tile, 0608 dng_abort_sniffer *sniffer); 0609 0610 }; 0611 0612 /*****************************************************************************/ 0613 0614 template <SIMDType simd> 0615 dng_limit_float_depth_task<simd>::dng_limit_float_depth_task 0616 (const dng_image &srcImage, 0617 dng_image &dstImage, 0618 uint32 bitDepth, 0619 real32 scale) 0620 0621 : dng_area_task ("dng_limit_float_depth_task") 0622 0623 , fSrcImage (srcImage) 0624 , fDstImage (dstImage) 0625 , fBitDepth (bitDepth) 0626 , fScale (scale) 0627 0628 { 0629 0630 } 0631 0632 /*****************************************************************************/ 0633 0634 template <SIMDType simd> 0635 void dng_limit_float_depth_task<simd>::Process (uint32 /* threadIndex */, 0636 const dng_rect &tile, 0637 dng_abort_sniffer * /* sniffer */) 0638 { 0639 0640 INTEL_COMPILER_NEEDED_NOTE 0641 0642 SET_CPU_FEATURE (simd); 0643 0644 dng_const_tile_buffer srcBuffer (fSrcImage, tile); 0645 dng_dirty_tile_buffer dstBuffer (fDstImage, tile); 0646 0647 uint32 count0 = tile.H (); 0648 uint32 count1 = tile.W (); 0649 uint32 count2 = fDstImage.Planes (); 0650 0651 int32 sStep0 = srcBuffer.fRowStep; 0652 int32 sStep1 = srcBuffer.fColStep; 0653 int32 sStep2 = srcBuffer.fPlaneStep; 0654 0655 int32 dStep0 = dstBuffer.fRowStep; 0656 int32 dStep1 = dstBuffer.fColStep; 0657 int32 dStep2 = dstBuffer.fPlaneStep; 0658 0659 const void *sPtr = srcBuffer.ConstPixel (tile.t, 0660 tile.l, 0661 0); 0662 0663 void *dPtr = dstBuffer.DirtyPixel (tile.t, 0664 tile.l, 0665 0); 0666 0667 OptimizeOrder (sPtr, 0668 dPtr, 0669 srcBuffer.fPixelSize, 0670 dstBuffer.fPixelSize, 0671 count0, 0672 count1, 0673 count2, 0674 sStep0, 0675 sStep1, 0676 sStep2, 0677 dStep0, 0678 dStep1, 0679 dStep2); 0680 0681 const real32 *sPtr0 = (const real32 *) sPtr; 0682 real32 *dPtr0 = ( real32 *) dPtr; 0683 0684 real32 scale = fScale; 0685 0686 bool limit16 = (fBitDepth == 16); 0687 bool limit24 = (fBitDepth == 24); 0688 0689 for (uint32 index0 = 0; index0 < count0; index0++) 0690 { 0691 0692 const real32 *sPtr1 = sPtr0; 0693 real32 *dPtr1 = dPtr0; 0694 0695 for (uint32 index1 = 0; index1 < count1; index1++) 0696 { 0697 0698 // If the scale is a NOP, and the data is packed solid, we can just do memory 0699 // copy. 0700 0701 if (scale == 1.0f && sStep2 == 1 && dStep2 == 1) 0702 { 0703 0704 if (dPtr1 != sPtr1) // srcImage != dstImage 0705 { 0706 0707 memcpy (dPtr1, sPtr1, count2 * (uint32) sizeof (real32)); 0708 0709 } 0710 0711 } 0712 0713 else 0714 { 0715 0716 const real32 *sPtr2 = sPtr1; 0717 real32 *dPtr2 = dPtr1; 0718 INTEL_PRAGMA_SIMD_ASSERT_VECLEN_FLOAT(simd) 0719 for (uint32 index2 = 0; index2 < count2; index2++) 0720 { 0721 0722 real32 x = sPtr2 [0]; 0723 0724 x *= scale; 0725 0726 dPtr2 [0] = x; 0727 0728 sPtr2 += sStep2; 0729 dPtr2 += dStep2; 0730 0731 } 0732 0733 } 0734 0735 // The data is now in the destination buffer. 0736 0737 if (limit16) 0738 { 0739 0740 //start by using intrinsic __m256 _mm256_cvtph_ps (__m128i a) 0741 //once the intrinsic is written, merge this branch with previous one 0742 0743 uint32 *dPtr2 = (uint32 *) dPtr1; 0744 0745 INTEL_PRAGMA_SIMD_ASSERT_VECLEN_INT32(simd) 0746 0747 for (uint32 index2 = 0; index2 < count2; index2++) 0748 { 0749 0750 uint32 x = dPtr2 [0]; 0751 0752 uint16 y = DNG_FloatToHalf (x); 0753 0754 x = DNG_HalfToFloat (y); 0755 0756 dPtr2 [0] = x; 0757 0758 dPtr2 += dStep2; 0759 0760 } 0761 0762 } 0763 0764 else if (limit24) 0765 { 0766 0767 uint32 *dPtr2 = (uint32 *) dPtr1; 0768 0769 for (uint32 index2 = 0; index2 < count2; index2++) 0770 { 0771 0772 uint32 x = dPtr2 [0]; 0773 0774 uint8 temp [3]; 0775 0776 DNG_FloatToFP24 (x, temp); 0777 0778 x = DNG_FP24ToFloat (temp); 0779 0780 dPtr2 [0] = x; 0781 0782 dPtr2 += dStep2; 0783 0784 } 0785 0786 } 0787 0788 sPtr1 += sStep1; 0789 dPtr1 += dStep1; 0790 0791 } 0792 0793 sPtr0 += sStep0; 0794 dPtr0 += dStep0; 0795 0796 } 0797 0798 } 0799 0800 /******************************************************************************/ 0801 0802 template <SIMDType simd> 0803 void LimitFloatBitDepth (dng_host &host, 0804 const dng_image &srcImage, 0805 dng_image &dstImage, 0806 uint32 bitDepth, 0807 real32 scale) 0808 { 0809 0810 DNG_ASSERT (srcImage.PixelType () == ttFloat, "Floating point image expected"); 0811 DNG_ASSERT (dstImage.PixelType () == ttFloat, "Floating point image expected"); 0812 0813 dng_limit_float_depth_task<simd> task (srcImage, 0814 dstImage, 0815 bitDepth, 0816 scale); 0817 0818 host.PerformAreaTask (task, dstImage.Bounds ()); 0819 0820 } 0821 0822 /*****************************************************************************/ 0823 0824 template 0825 void LimitFloatBitDepth<Scalar> (dng_host &host, 0826 const dng_image &srcImage, 0827 dng_image &dstImage, 0828 uint32 bitDepth, 0829 real32 scale); 0830 0831 /*****************************************************************************/ 0832 0833 #if qDNGIntelCompiler 0834 0835 template 0836 void LimitFloatBitDepth<AVX2> (dng_host &host, 0837 const dng_image &srcImage, 0838 dng_image &dstImage, 0839 uint32 bitDepth, 0840 real32 scale); 0841 0842 #endif // qDNGIntelCompiler 0843 0844 /*****************************************************************************/ 0845 0846 void LimitFloatBitDepth (dng_host &host, 0847 const dng_image &srcImage, 0848 dng_image &dstImage, 0849 uint32 bitDepth, 0850 real32 scale) 0851 { 0852 0853 // Kludge: Turning this off for now because the AVX2 path produces 0854 // slightly different results from the Scalar routine causing a mis-match 0855 // in raw digest values when building HDR merge result negatives which 0856 // causes the client to display a "file appears to be damaged" warning. 0857 // -bury 11/13/2017 0858 0859 #if (qDNGIntelCompiler && qDNGExperimental && 0) 0860 0861 if (gDNGMaxSIMD >= AVX2) 0862 { 0863 0864 LimitFloatBitDepth<AVX2> (host, 0865 srcImage, 0866 dstImage, 0867 bitDepth, 0868 scale); 0869 0870 } 0871 0872 else 0873 0874 #endif // qDNGIntelCompiler && qDNGExperimental 0875 0876 { 0877 0878 LimitFloatBitDepth<Scalar> (host, 0879 srcImage, 0880 dstImage, 0881 bitDepth, 0882 scale); 0883 0884 } 0885 0886 } 0887 0888 /*****************************************************************************/