File indexing completed on 2025-01-19 03:55:15
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_resample.h" 0010 0011 #include "dng_assertions.h" 0012 #include "dng_bottlenecks.h" 0013 #include "dng_filter_task.h" 0014 #include "dng_host.h" 0015 #include "dng_image.h" 0016 #include "dng_memory.h" 0017 #include "dng_pixel_buffer.h" 0018 #include "dng_safe_arithmetic.h" 0019 #include "dng_tag_types.h" 0020 #include "dng_utils.h" 0021 0022 /******************************************************************************/ 0023 0024 real64 dng_resample_bicubic::Extent () const 0025 { 0026 0027 return 2.0; 0028 0029 } 0030 0031 /******************************************************************************/ 0032 0033 real64 dng_resample_bicubic::Evaluate (real64 x) const 0034 { 0035 0036 const real64 A = -0.75; 0037 0038 x = Abs_real64 (x); 0039 0040 if (x >= 2.0) 0041 return 0.0; 0042 0043 else if (x >= 1.0) 0044 return (((A * x - 5.0 * A) * x + 8.0 * A) * x - 4.0 * A); 0045 0046 else 0047 return (((A + 2.0) * x - (A + 3.0)) * x * x + 1.0); 0048 0049 } 0050 0051 /******************************************************************************/ 0052 0053 const dng_resample_function & dng_resample_bicubic::Get () 0054 { 0055 0056 static dng_resample_bicubic static_dng_resample_bicubic; 0057 0058 return static_dng_resample_bicubic; 0059 0060 } 0061 0062 /*****************************************************************************/ 0063 0064 dng_resample_coords::dng_resample_coords () 0065 0066 : fOrigin (0) 0067 , fCoords () 0068 0069 { 0070 0071 } 0072 0073 /*****************************************************************************/ 0074 0075 dng_resample_coords::~dng_resample_coords () 0076 { 0077 0078 } 0079 0080 /*****************************************************************************/ 0081 0082 void dng_resample_coords::Initialize (int32 srcOrigin, 0083 int32 dstOrigin, 0084 uint32 srcCount, 0085 uint32 dstCount, 0086 dng_memory_allocator &allocator) 0087 { 0088 0089 fOrigin = dstOrigin; 0090 0091 uint32 dstEntries = 0; 0092 uint32 bufferSize = 0; 0093 0094 if (!RoundUpUint32ToMultiple (dstCount, 8, &dstEntries) || 0095 !SafeUint32Mult (dstEntries, sizeof (int32), &bufferSize)) 0096 { 0097 0098 ThrowOverflow ("Arithmetic overflow computing size for coordinate " 0099 "buffer"); 0100 0101 } 0102 0103 fCoords.Reset (allocator.Allocate (bufferSize)); 0104 0105 int32 *coords = fCoords->Buffer_int32 (); 0106 0107 real64 invScale = (real64) srcCount / 0108 (real64) dstCount; 0109 0110 for (uint32 j = 0; j < dstCount; j++) 0111 { 0112 0113 real64 x = (real64) j + 0.5; 0114 0115 real64 y = x * invScale - 0.5 + (real64) srcOrigin; 0116 0117 coords [j] = Round_int32 (y * (real64) kResampleSubsampleCount); 0118 0119 } 0120 0121 // Pad out table by replicating last entry. 0122 0123 for (uint32 k = dstCount; k < dstEntries; k++) 0124 { 0125 0126 coords [k] = coords [dstCount - 1]; 0127 0128 } 0129 0130 } 0131 0132 /*****************************************************************************/ 0133 0134 dng_resample_weights::dng_resample_weights () 0135 0136 : fRadius (0) 0137 0138 , fWeightStep (0) 0139 0140 , fWeights32 () 0141 , fWeights16 () 0142 0143 { 0144 0145 } 0146 0147 /*****************************************************************************/ 0148 0149 dng_resample_weights::~dng_resample_weights () 0150 { 0151 0152 } 0153 0154 /*****************************************************************************/ 0155 0156 void dng_resample_weights::Initialize (real64 scale, 0157 const dng_resample_function &kernel, 0158 dng_memory_allocator &allocator) 0159 { 0160 0161 uint32 j; 0162 0163 // We only adjust the kernel size for scale factors less than 1.0. 0164 0165 scale = Min_real64 (scale, 1.0); 0166 0167 // Find radius of this kernel. 0168 0169 fRadius = (uint32) (kernel.Extent () / scale + 0.9999); 0170 0171 // Width is twice the radius. 0172 0173 uint32 width = fRadius * 2; 0174 0175 // Round to each set to weights to a multiple of 8 entries. 0176 0177 if (!RoundUpUint32ToMultiple (width, 8, &fWeightStep)) 0178 { 0179 0180 ThrowOverflow ("Arithmetic overflow computing fWeightStep"); 0181 0182 } 0183 0184 // Allocate and zero weight tables. 0185 0186 uint32 bufferSize = 0; 0187 0188 if (!SafeUint32Mult (fWeightStep, kResampleSubsampleCount, &bufferSize) || 0189 !SafeUint32Mult (bufferSize, (uint32) sizeof (real32), &bufferSize)) 0190 { 0191 0192 ThrowOverflow ("Arithmetic overflow computing buffer size."); 0193 0194 } 0195 0196 fWeights32.Reset (allocator.Allocate (bufferSize)); 0197 0198 DoZeroBytes (fWeights32->Buffer (), 0199 fWeights32->LogicalSize ()); 0200 0201 if (!SafeUint32Mult (fWeightStep, kResampleSubsampleCount, &bufferSize) || 0202 !SafeUint32Mult (bufferSize, (uint32) sizeof (int16), &bufferSize)) 0203 { 0204 0205 ThrowOverflow ("Arithmetic overflow computing buffer size."); 0206 0207 } 0208 0209 fWeights16.Reset (allocator.Allocate (bufferSize)); 0210 0211 DoZeroBytes (fWeights16->Buffer (), 0212 fWeights16->LogicalSize ()); 0213 0214 // Compute kernel for each subsample values. 0215 0216 for (uint32 sample = 0; sample < kResampleSubsampleCount; sample++) 0217 { 0218 0219 real64 fract = sample * (1.0 / (real64) kResampleSubsampleCount); 0220 0221 real32 *w32 = fWeights32->Buffer_real32 () + fWeightStep * sample; 0222 0223 // Evaluate kernel function for 32 bit weights. 0224 0225 { 0226 0227 real64 t32 = 0.0; 0228 0229 for (j = 0; j < width; j++) 0230 { 0231 0232 int32 k = (int32) j - (int32) fRadius + 1; 0233 0234 real64 x = (k - fract) * scale; 0235 0236 w32 [j] = (real32) kernel.Evaluate (x); 0237 0238 t32 += w32 [j]; 0239 0240 } 0241 0242 // Scale 32 bit weights so total of weights is 1.0. 0243 0244 real32 s32 = (real32) (1.0 / t32); 0245 0246 for (j = 0; j < width; j++) 0247 { 0248 0249 w32 [j] *= s32; 0250 0251 } 0252 0253 } 0254 0255 // Round off 32 bit weights to 16 bit weights. 0256 0257 { 0258 0259 int16 *w16 = fWeights16->Buffer_int16 () + fWeightStep * sample; 0260 0261 int32 t16 = 0; 0262 0263 for (j = 0; j < width; j++) 0264 { 0265 0266 w16 [j] = (int16) Round_int32 (w32 [j] * 16384.0); 0267 0268 t16 += w16 [j]; 0269 0270 } 0271 0272 // Adjust center entry for any round off error so total is 0273 // exactly 16384. 0274 0275 w16 [fRadius - (fract >= 0.5 ? 0 : 1)] += (int16) (16384 - t16); 0276 0277 } 0278 0279 } 0280 0281 } 0282 0283 /*****************************************************************************/ 0284 0285 dng_resample_weights_2d::dng_resample_weights_2d () 0286 0287 : fRadius (0) 0288 0289 , fRowStep (0) 0290 , fColStep (0) 0291 0292 , fWeights32 () 0293 , fWeights16 () 0294 0295 { 0296 0297 } 0298 0299 /*****************************************************************************/ 0300 0301 dng_resample_weights_2d::~dng_resample_weights_2d () 0302 { 0303 0304 } 0305 0306 /*****************************************************************************/ 0307 0308 void dng_resample_weights_2d::Initialize (const dng_resample_function &kernel, 0309 dng_memory_allocator &allocator) 0310 { 0311 0312 // Find radius of this kernel. Unlike with 1d resample weights (see 0313 // dng_resample_weights), we never scale up the kernel size. 0314 0315 fRadius = (uint32) (kernel.Extent () + 0.9999); 0316 0317 // Width is twice the radius. 0318 0319 uint32 width = 0; 0320 uint32 widthSqr = 0; 0321 uint32 step = 0; 0322 0323 if (!SafeUint32Mult (fRadius, 2, &width) || 0324 !SafeUint32Mult (width, width, &widthSqr) || 0325 !RoundUpUint32ToMultiple (widthSqr, 8, &step) || 0326 !SafeUint32Mult (step, kResampleSubsampleCount2D, &fRowStep)) 0327 { 0328 0329 ThrowOverflow ("Arithmetic overflow computing row step."); 0330 0331 } 0332 0333 fColStep = step; 0334 0335 // Allocate and zero weight tables. 0336 0337 uint32 bufferSize = 0; 0338 0339 if (!SafeUint32Mult (step, kResampleSubsampleCount2D, &bufferSize) || 0340 !SafeUint32Mult (bufferSize, kResampleSubsampleCount2D, &bufferSize) || 0341 !SafeUint32Mult (bufferSize, (uint32) sizeof (real32), &bufferSize)) 0342 { 0343 0344 ThrowOverflow ("Arithmetic overflow computing buffer size."); 0345 0346 } 0347 0348 fWeights32.Reset (allocator.Allocate (bufferSize)); 0349 0350 DoZeroBytes (fWeights32->Buffer (), 0351 fWeights32->LogicalSize ()); 0352 0353 if (!SafeUint32Mult (step, kResampleSubsampleCount2D, &bufferSize) || 0354 !SafeUint32Mult (bufferSize, kResampleSubsampleCount2D, &bufferSize) || 0355 !SafeUint32Mult (bufferSize, (uint32) sizeof (int16), &bufferSize)) 0356 { 0357 0358 ThrowOverflow ("Arithmetic overflow computing buffer size."); 0359 0360 } 0361 0362 fWeights16.Reset (allocator.Allocate (bufferSize)); 0363 0364 DoZeroBytes (fWeights16->Buffer (), 0365 fWeights16->LogicalSize ()); 0366 0367 // Compute kernel for each subsample values. 0368 0369 for (uint32 y = 0; y < kResampleSubsampleCount2D; y++) 0370 { 0371 0372 real64 yFract = y * (1.0 / (real64) kResampleSubsampleCount2D); 0373 0374 for (uint32 x = 0; x < kResampleSubsampleCount2D; x++) 0375 { 0376 0377 real64 xFract = x * (1.0 / (real64) kResampleSubsampleCount2D); 0378 0379 real32 *w32 = (real32 *) Weights32 (dng_point ((int32) y, 0380 (int32) x)); 0381 0382 // Evaluate kernel function for 32 bit weights. 0383 0384 { 0385 0386 real64 t32 = 0.0; 0387 0388 uint32 index = 0; 0389 0390 for (uint32 i = 0; i < width; i++) 0391 { 0392 0393 int32 yInt = ((int32) i) - (int32) fRadius + 1; 0394 real64 yPos = yInt - yFract; 0395 0396 for (uint32 j = 0; j < width; j++) 0397 { 0398 0399 int32 xInt = ((int32) j) - (int32) fRadius + 1; 0400 real64 xPos = xInt - xFract; 0401 0402 #if 0 0403 0404 // Radial. 0405 0406 real64 dy2 = yPos * yPos; 0407 real64 dx2 = xPos * xPos; 0408 0409 real64 r = sqrt (dx2 + dy2); 0410 0411 w32 [index] = (real32) kernel.Evaluate (r); 0412 0413 #else 0414 0415 // Separable. 0416 0417 w32 [index] = (real32) kernel.Evaluate (xPos) * 0418 (real32) kernel.Evaluate (yPos); 0419 0420 #endif 0421 0422 t32 += w32 [index]; 0423 0424 index++; 0425 0426 } 0427 0428 } 0429 0430 // Scale 32 bit weights so total of weights is 1.0. 0431 0432 const real32 s32 = (real32) (1.0 / t32); 0433 0434 for (uint32 i = 0; i < widthSqr; i++) 0435 { 0436 0437 w32 [i] *= s32; 0438 0439 } 0440 0441 } 0442 0443 // Round off 32 bit weights to 16 bit weights. 0444 0445 { 0446 0447 int16 *w16 = (int16 *) Weights16 (dng_point ((int32) y, 0448 (int32) x)); 0449 0450 int32 t16 = 0; 0451 0452 for (uint32 j = 0; j < widthSqr; j++) 0453 { 0454 0455 w16 [j] = (int16) Round_int32 (w32 [j] * 16384.0); 0456 0457 t16 += w16 [j]; 0458 0459 } 0460 0461 // Adjust one of the center entries for any round off error so total 0462 // is exactly 16384. 0463 0464 const uint32 xOffset = fRadius - ((xFract >= 0.5) ? 0 : 1); 0465 const uint32 yOffset = fRadius - ((yFract >= 0.5) ? 0 : 1); 0466 const uint32 centerOffset = width * yOffset + xOffset; 0467 0468 w16 [centerOffset] += (int16) (16384 - t16); 0469 0470 } 0471 0472 } 0473 0474 } 0475 0476 } 0477 0478 /*****************************************************************************/ 0479 0480 class dng_resample_task: public dng_filter_task 0481 { 0482 0483 protected: 0484 0485 dng_rect fSrcBounds; 0486 dng_rect fDstBounds; 0487 0488 const dng_resample_function &fKernel; 0489 0490 real64 fRowScale; 0491 real64 fColScale; 0492 0493 dng_resample_coords fRowCoords; 0494 dng_resample_coords fColCoords; 0495 0496 dng_resample_weights fWeightsV; 0497 dng_resample_weights fWeightsH; 0498 0499 dng_point fSrcTileSize; 0500 0501 AutoPtr<dng_memory_block> fTempBuffer [kMaxMPThreads]; 0502 0503 public: 0504 0505 dng_resample_task (const dng_image &srcImage, 0506 dng_image &dstImage, 0507 const dng_rect &srcBounds, 0508 const dng_rect &dstBounds, 0509 const dng_resample_function &kernel); 0510 0511 virtual dng_rect SrcArea (const dng_rect &dstArea); 0512 0513 virtual dng_point SrcTileSize (const dng_point &dstTileSize); 0514 0515 virtual void Start (uint32 threadCount, 0516 const dng_rect &dstArea, 0517 const dng_point &tileSize, 0518 dng_memory_allocator *allocator, 0519 dng_abort_sniffer *sniffer); 0520 0521 virtual void ProcessArea (uint32 threadIndex, 0522 dng_pixel_buffer &srcBuffer, 0523 dng_pixel_buffer &dstBuffer); 0524 0525 }; 0526 0527 /*****************************************************************************/ 0528 0529 dng_resample_task::dng_resample_task (const dng_image &srcImage, 0530 dng_image &dstImage, 0531 const dng_rect &srcBounds, 0532 const dng_rect &dstBounds, 0533 const dng_resample_function &kernel) 0534 0535 : dng_filter_task ("dng_resample_task", 0536 srcImage, 0537 dstImage) 0538 0539 , fSrcBounds (srcBounds) 0540 , fDstBounds (dstBounds) 0541 0542 , fKernel (kernel) 0543 0544 , fRowScale (dstBounds.H () / (real64) srcBounds.H ()) 0545 , fColScale (dstBounds.W () / (real64) srcBounds.W ()) 0546 0547 , fRowCoords () 0548 , fColCoords () 0549 0550 , fWeightsV () 0551 , fWeightsH () 0552 0553 , fSrcTileSize () 0554 0555 { 0556 0557 if (srcImage.PixelSize () <= 2 && 0558 dstImage.PixelSize () <= 2 && 0559 srcImage.PixelRange () == dstImage.PixelRange ()) 0560 { 0561 fSrcPixelType = ttShort; 0562 fDstPixelType = ttShort; 0563 } 0564 0565 else 0566 { 0567 fSrcPixelType = ttFloat; 0568 fDstPixelType = ttFloat; 0569 } 0570 0571 fUnitCell = dng_point (8, 8); 0572 0573 fMaxTileSize.v = Pin_int32 (fUnitCell.v, 0574 Round_int32 (fMaxTileSize.v * fRowScale), 0575 fMaxTileSize.v); 0576 0577 fMaxTileSize.h = Pin_int32 (fUnitCell.h, 0578 Round_int32 (fMaxTileSize.h * fColScale), 0579 fMaxTileSize.h); 0580 0581 } 0582 0583 /*****************************************************************************/ 0584 0585 DNG_ATTRIB_NO_SANITIZE("unsigned-integer-overflow") 0586 dng_rect dng_resample_task::SrcArea (const dng_rect &dstArea) 0587 { 0588 0589 int32 offsetV = fWeightsV.Offset (); 0590 int32 offsetH = fWeightsH.Offset (); 0591 0592 uint32 widthV = fWeightsV.Width (); 0593 uint32 widthH = fWeightsH.Width (); 0594 0595 dng_rect srcArea; 0596 0597 srcArea.t = fRowCoords.Pixel (dstArea.t) + offsetV; 0598 srcArea.l = fColCoords.Pixel (dstArea.l) + offsetH; 0599 0600 srcArea.b = fRowCoords.Pixel (dstArea.b - 1) + offsetV + widthV; 0601 srcArea.r = fColCoords.Pixel (dstArea.r - 1) + offsetH + widthH; 0602 0603 return srcArea; 0604 0605 } 0606 0607 /*****************************************************************************/ 0608 0609 dng_point dng_resample_task::SrcTileSize (const dng_point & /* dstTileSize */) 0610 { 0611 0612 return fSrcTileSize; 0613 0614 } 0615 0616 /*****************************************************************************/ 0617 0618 void dng_resample_task::Start (uint32 threadCount, 0619 const dng_rect &dstArea, 0620 const dng_point &tileSize, 0621 dng_memory_allocator *allocator, 0622 dng_abort_sniffer *sniffer) 0623 { 0624 0625 // Compute sub-pixel resolution coordinates in the source image for 0626 // each row and column of the destination area. 0627 0628 fRowCoords.Initialize (fSrcBounds.t, 0629 fDstBounds.t, 0630 fSrcBounds.H (), 0631 fDstBounds.H (), 0632 *allocator); 0633 0634 fColCoords.Initialize (fSrcBounds.l, 0635 fDstBounds.l, 0636 fSrcBounds.W (), 0637 fDstBounds.W (), 0638 *allocator); 0639 0640 // Compute resampling kernels. 0641 0642 fWeightsV.Initialize (fRowScale, 0643 fKernel, 0644 *allocator); 0645 0646 fWeightsH.Initialize (fColScale, 0647 fKernel, 0648 *allocator); 0649 0650 // Find upper bound on source source tile. 0651 0652 fSrcTileSize.v = Round_int32 (tileSize.v / fRowScale) + fWeightsV.Width () + 2; 0653 fSrcTileSize.h = Round_int32 (tileSize.h / fColScale) + fWeightsH.Width () + 2; 0654 0655 // Allocate temp buffers. 0656 0657 uint32 tempBufferSize = 0; 0658 0659 if (!RoundUpUint32ToMultiple (fSrcTileSize.h, 8, &tempBufferSize) || 0660 !SafeUint32Mult (tempBufferSize, 0661 static_cast<uint32> (sizeof (real32)), 0662 &tempBufferSize)) 0663 { 0664 0665 ThrowOverflow ("Arithmetic overflow computing buffer size."); 0666 0667 } 0668 0669 for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++) 0670 { 0671 0672 fTempBuffer [threadIndex] . Reset (allocator->Allocate (tempBufferSize)); 0673 0674 } 0675 0676 // Allocate the pixel buffers. 0677 0678 dng_filter_task::Start (threadCount, 0679 dstArea, 0680 tileSize, 0681 allocator, 0682 sniffer); 0683 0684 } 0685 0686 /*****************************************************************************/ 0687 0688 void dng_resample_task::ProcessArea (uint32 threadIndex, 0689 dng_pixel_buffer &srcBuffer, 0690 dng_pixel_buffer &dstBuffer) 0691 { 0692 0693 dng_rect srcArea = srcBuffer.fArea; 0694 dng_rect dstArea = dstBuffer.fArea; 0695 0696 uint32 srcCols = srcArea.W (); 0697 uint32 dstCols = dstArea.W (); 0698 0699 uint32 widthV = fWeightsV.Width (); 0700 uint32 widthH = fWeightsH.Width (); 0701 0702 int32 offsetV = fWeightsV.Offset (); 0703 int32 offsetH = fWeightsH.Offset (); 0704 0705 uint32 stepH = fWeightsH.Step (); 0706 0707 const int32 *rowCoords = fRowCoords.Coords (0 ); 0708 const int32 *colCoords = fColCoords.Coords (dstArea.l); 0709 0710 if (fSrcPixelType == ttFloat) 0711 { 0712 0713 const real32 *weightsH = fWeightsH.Weights32 (0); 0714 0715 real32 *tPtr = fTempBuffer [threadIndex]->Buffer_real32 (); 0716 0717 real32 *ttPtr = tPtr + offsetH - srcArea.l; 0718 0719 for (int32 dstRow = dstArea.t; dstRow < dstArea.b; dstRow++) 0720 { 0721 0722 int32 rowCoord = rowCoords [dstRow]; 0723 0724 int32 rowFract = rowCoord & kResampleSubsampleMask; 0725 0726 const real32 *weightsV = fWeightsV.Weights32 (rowFract); 0727 0728 int32 srcRow = (rowCoord >> kResampleSubsampleBits) + offsetV; 0729 0730 for (uint32 plane = 0; plane < dstBuffer.fPlanes; plane++) 0731 { 0732 0733 const real32 *sPtr = srcBuffer.ConstPixel_real32 (srcRow, 0734 srcArea.l, 0735 plane); 0736 0737 DoResampleDown32 (sPtr, 0738 tPtr, 0739 srcCols, 0740 srcBuffer.fRowStep, 0741 weightsV, 0742 widthV); 0743 0744 real32 *dPtr = dstBuffer.DirtyPixel_real32 (dstRow, 0745 dstArea.l, 0746 plane); 0747 0748 DoResampleAcross32 (ttPtr, 0749 dPtr, 0750 dstCols, 0751 colCoords, 0752 weightsH, 0753 widthH, 0754 stepH); 0755 0756 } 0757 0758 } 0759 0760 } 0761 0762 else 0763 { 0764 0765 const int16 *weightsH = fWeightsH.Weights16 (0); 0766 0767 uint16 *tPtr = fTempBuffer [threadIndex]->Buffer_uint16 (); 0768 0769 uint16 *ttPtr = tPtr + offsetH - srcArea.l; 0770 0771 uint32 pixelRange = fDstImage.PixelRange (); 0772 0773 for (int32 dstRow = dstArea.t; dstRow < dstArea.b; dstRow++) 0774 { 0775 0776 int32 rowCoord = rowCoords [dstRow]; 0777 0778 int32 rowFract = rowCoord & kResampleSubsampleMask; 0779 0780 const int16 *weightsV = fWeightsV.Weights16 (rowFract); 0781 0782 int32 srcRow = (rowCoord >> kResampleSubsampleBits) + offsetV; 0783 0784 for (uint32 plane = 0; plane < dstBuffer.fPlanes; plane++) 0785 { 0786 0787 const uint16 *sPtr = srcBuffer.ConstPixel_uint16 (srcRow, 0788 srcArea.l, 0789 plane); 0790 0791 DoResampleDown16 (sPtr, 0792 tPtr, 0793 srcCols, 0794 srcBuffer.fRowStep, 0795 weightsV, 0796 widthV, 0797 pixelRange); 0798 0799 uint16 *dPtr = dstBuffer.DirtyPixel_uint16 (dstRow, 0800 dstArea.l, 0801 plane); 0802 0803 DoResampleAcross16 (ttPtr, 0804 dPtr, 0805 dstCols, 0806 colCoords, 0807 weightsH, 0808 widthH, 0809 stepH, 0810 pixelRange); 0811 0812 } 0813 0814 } 0815 0816 } 0817 0818 } 0819 0820 /*****************************************************************************/ 0821 0822 void ResampleImage (dng_host &host, 0823 const dng_image &srcImage, 0824 dng_image &dstImage, 0825 const dng_rect &srcBounds, 0826 const dng_rect &dstBounds, 0827 const dng_resample_function &kernel) 0828 { 0829 0830 dng_resample_task task (srcImage, 0831 dstImage, 0832 srcBounds, 0833 dstBounds, 0834 kernel); 0835 0836 host.PerformAreaTask (task, 0837 dstBounds); 0838 0839 } 0840 0841 /*****************************************************************************/