1 /*****************************************************************************/
2 // Copyright 2006 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE: Adobe permits you to use, modify, and distribute this file in
6 // accordance with the terms of the Adobe license agreement accompanying it.
7 /*****************************************************************************/
8
9 /* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_resample.cpp#1 $ */
10 /* $DateTime: 2012/05/30 13:28:51 $ */
11 /* $Change: 832332 $ */
12 /* $Author: tknoll $ */
13
14 /*****************************************************************************/
15
16 #include "dng_resample.h"
17
18 #include "dng_assertions.h"
19 #include "dng_bottlenecks.h"
20 #include "dng_filter_task.h"
21 #include "dng_host.h"
22 #include "dng_image.h"
23 #include "dng_memory.h"
24 #include "dng_pixel_buffer.h"
25 #include "dng_safe_arithmetic.h"
26 #include "dng_tag_types.h"
27 #include "dng_utils.h"
28
29 /******************************************************************************/
30
Extent() const31 real64 dng_resample_bicubic::Extent () const
32 {
33
34 return 2.0;
35
36 }
37
38 /******************************************************************************/
39
Evaluate(real64 x) const40 real64 dng_resample_bicubic::Evaluate (real64 x) const
41 {
42
43 const real64 A = -0.75;
44
45 x = Abs_real64 (x);
46
47 if (x >= 2.0)
48 return 0.0;
49
50 else if (x >= 1.0)
51 return (((A * x - 5.0 * A) * x + 8.0 * A) * x - 4.0 * A);
52
53 else
54 return (((A + 2.0) * x - (A + 3.0)) * x * x + 1.0);
55
56 }
57
58 /******************************************************************************/
59
Get()60 const dng_resample_function & dng_resample_bicubic::Get ()
61 {
62
63 static dng_resample_bicubic static_dng_resample_bicubic;
64
65 return static_dng_resample_bicubic;
66
67 }
68
69 /*****************************************************************************/
70
dng_resample_coords()71 dng_resample_coords::dng_resample_coords ()
72
73 : fOrigin (0)
74 , fCoords ()
75
76 {
77
78 }
79
80 /*****************************************************************************/
81
~dng_resample_coords()82 dng_resample_coords::~dng_resample_coords ()
83 {
84
85 }
86
87 /*****************************************************************************/
88
Initialize(int32 srcOrigin,int32 dstOrigin,uint32 srcCount,uint32 dstCount,dng_memory_allocator & allocator)89 void dng_resample_coords::Initialize (int32 srcOrigin,
90 int32 dstOrigin,
91 uint32 srcCount,
92 uint32 dstCount,
93 dng_memory_allocator &allocator)
94 {
95
96 fOrigin = dstOrigin;
97
98 uint32 dstEntries = 0;
99 uint32 bufferSize = 0;
100 if (!RoundUpUint32ToMultiple(dstCount, 8, &dstEntries) ||
101 !SafeUint32Mult(dstEntries, sizeof(int32), &bufferSize)) {
102 ThrowMemoryFull("Arithmetic overflow computing size for coordinate "
103 "buffer");
104 }
105 fCoords.Reset (allocator.Allocate (bufferSize));
106
107 int32 *coords = fCoords->Buffer_int32 ();
108
109 real64 invScale = (real64) srcCount /
110 (real64) dstCount;
111
112 for (uint32 j = 0; j < dstCount; j++)
113 {
114
115 real64 x = (real64) j + 0.5;
116
117 real64 y = x * invScale - 0.5 + (real64) srcOrigin;
118
119 coords [j] = Round_int32 (y * (real64) kResampleSubsampleCount);
120
121 }
122
123 // Pad out table by replicating last entry.
124
125 for (uint32 k = dstCount; k < dstEntries; k++)
126 {
127
128 coords [k] = coords [dstCount - 1];
129
130 }
131
132 }
133
134 /*****************************************************************************/
135
dng_resample_weights()136 dng_resample_weights::dng_resample_weights ()
137
138 : fRadius (0)
139
140 , fWeightStep (0)
141
142 , fWeights32 ()
143 , fWeights16 ()
144
145 {
146
147 }
148
149 /*****************************************************************************/
150
~dng_resample_weights()151 dng_resample_weights::~dng_resample_weights ()
152 {
153
154 }
155
156 /*****************************************************************************/
157
Initialize(real64 scale,const dng_resample_function & kernel,dng_memory_allocator & allocator)158 void dng_resample_weights::Initialize (real64 scale,
159 const dng_resample_function &kernel,
160 dng_memory_allocator &allocator)
161 {
162
163 uint32 j;
164
165 // We only adjust the kernel size for scale factors less than 1.0.
166
167 scale = Min_real64 (scale, 1.0);
168
169 // Find radius of this kernel.
170
171 fRadius = (uint32) (kernel.Extent () / scale + 0.9999);
172
173 // Width is twice the radius.
174
175 uint32 width = fRadius * 2;
176
177 // Round to each set to weights to a multiple of 8 entries.
178
179 if (!RoundUpUint32ToMultiple (width, 8, &fWeightStep))
180 {
181
182 ThrowMemoryFull ("Arithmetic overflow computing fWeightStep");
183
184 }
185
186 // Allocate and zero weight tables.
187
188 uint32 bufferSize = 0;
189
190 if (!SafeUint32Mult (fWeightStep, kResampleSubsampleCount, &bufferSize) ||
191 !SafeUint32Mult (bufferSize, (uint32) sizeof (real32), &bufferSize))
192 {
193
194 ThrowMemoryFull("Arithmetic overflow computing buffer size.");
195
196 }
197
198 fWeights32.Reset (allocator.Allocate (bufferSize));
199
200 DoZeroBytes (fWeights32->Buffer (),
201 fWeights32->LogicalSize ());
202
203 if (!SafeUint32Mult (fWeightStep, kResampleSubsampleCount, &bufferSize) ||
204 !SafeUint32Mult (bufferSize, (uint32) sizeof (int16), &bufferSize))
205 {
206
207 ThrowMemoryFull("Arithmetic overflow computing buffer size.");
208
209 }
210
211 fWeights16.Reset (allocator.Allocate (bufferSize));
212
213 DoZeroBytes (fWeights16->Buffer (),
214 fWeights16->LogicalSize ());
215
216 // Compute kernel for each subsample values.
217
218 for (uint32 sample = 0; sample < kResampleSubsampleCount; sample++)
219 {
220
221 real64 fract = sample * (1.0 / (real64) kResampleSubsampleCount);
222
223 real32 *w32 = fWeights32->Buffer_real32 () + fWeightStep * sample;
224
225 // Evaluate kernel function for 32 bit weights.
226
227 {
228
229 real64 t32 = 0.0;
230
231 for (j = 0; j < width; j++)
232 {
233
234 int32 k = (int32) j - (int32) fRadius + 1;
235
236 real64 x = (k - fract) * scale;
237
238 w32 [j] = (real32) kernel.Evaluate (x);
239
240 t32 += w32 [j];
241
242 }
243
244 // Scale 32 bit weights so total of weights is 1.0.
245
246 real32 s32 = (real32) (1.0 / t32);
247
248 for (j = 0; j < width; j++)
249 {
250
251 w32 [j] *= s32;
252
253 }
254
255 }
256
257 // Round off 32 bit weights to 16 bit weights.
258
259 {
260
261 int16 *w16 = fWeights16->Buffer_int16 () + fWeightStep * sample;
262
263 int32 t16 = 0;
264
265 for (j = 0; j < width; j++)
266 {
267
268 w16 [j] = (int16) Round_int32 (w32 [j] * 16384.0);
269
270 t16 += w16 [j];
271
272 }
273
274 // Adjust center entry for any round off error so total is
275 // exactly 16384.
276
277 w16 [fRadius - (fract >= 0.5 ? 0 : 1)] += (int16) (16384 - t16);
278
279 }
280
281 }
282
283 }
284
285 /*****************************************************************************/
286
dng_resample_weights_2d()287 dng_resample_weights_2d::dng_resample_weights_2d ()
288
289 : fRadius (0)
290
291 , fRowStep (0)
292 , fColStep (0)
293
294 , fWeights32 ()
295 , fWeights16 ()
296
297 {
298
299 }
300
301 /*****************************************************************************/
302
~dng_resample_weights_2d()303 dng_resample_weights_2d::~dng_resample_weights_2d ()
304 {
305
306 }
307
308 /*****************************************************************************/
309
Initialize(const dng_resample_function & kernel,dng_memory_allocator & allocator)310 void dng_resample_weights_2d::Initialize (const dng_resample_function &kernel,
311 dng_memory_allocator &allocator)
312 {
313
314 // Find radius of this kernel. Unlike with 1d resample weights (see
315 // dng_resample_weights), we never scale up the kernel size.
316
317 fRadius = (uint32) (kernel.Extent () + 0.9999);
318
319 // Width is twice the radius.
320
321 uint32 width = 0;
322 uint32 widthSqr = 0;
323 uint32 step = 0;
324
325 if (!SafeUint32Mult (fRadius, 2, &width) ||
326 !SafeUint32Mult (width, width, &widthSqr) ||
327 !RoundUpUint32ToMultiple (widthSqr, 8, &step) ||
328 !SafeUint32Mult (step, kResampleSubsampleCount2D, &fRowStep))
329 {
330
331 ThrowMemoryFull ("Arithmetic overflow computing row step.");
332
333 }
334
335 fColStep = step;
336
337 // Allocate and zero weight tables.
338
339 uint32 bufferSize = 0;
340
341 if (!SafeUint32Mult (step, kResampleSubsampleCount2D, &bufferSize) ||
342 !SafeUint32Mult (bufferSize, kResampleSubsampleCount2D, &bufferSize) ||
343 !SafeUint32Mult (bufferSize, (uint32) sizeof (real32), &bufferSize))
344 {
345
346 ThrowMemoryFull ("Arithmetic overflow computing buffer size.");
347
348 }
349
350 fWeights32.Reset (allocator.Allocate (bufferSize));
351
352 DoZeroBytes (fWeights32->Buffer (),
353 fWeights32->LogicalSize ());
354
355
356 if (!SafeUint32Mult (step, kResampleSubsampleCount2D, &bufferSize) ||
357 !SafeUint32Mult (bufferSize, kResampleSubsampleCount2D, &bufferSize) ||
358 !SafeUint32Mult (bufferSize, (uint32) sizeof (int16), &bufferSize))
359 {
360
361 ThrowMemoryFull ("Arithmetic overflow computing buffer size.");
362
363 }
364
365 fWeights16.Reset (allocator.Allocate (bufferSize));
366
367 DoZeroBytes (fWeights16->Buffer (),
368 fWeights16->LogicalSize ());
369
370 // Compute kernel for each subsample values.
371
372 for (uint32 y = 0; y < kResampleSubsampleCount2D; y++)
373 {
374
375 real64 yFract = y * (1.0 / (real64) kResampleSubsampleCount2D);
376
377 for (uint32 x = 0; x < kResampleSubsampleCount2D; x++)
378 {
379
380 real64 xFract = x * (1.0 / (real64) kResampleSubsampleCount2D);
381
382 real32 *w32 = (real32 *) Weights32 (dng_point ((int32) y,
383 (int32) x));
384
385 // Evaluate kernel function for 32 bit weights.
386
387 {
388
389 real64 t32 = 0.0;
390
391 uint32 index = 0;
392
393 for (uint32 i = 0; i < width; i++)
394 {
395
396 int32 yInt = ((int32) i) - (int32) fRadius + 1;
397 real64 yPos = yInt - yFract;
398
399 for (uint32 j = 0; j < width; j++)
400 {
401
402 int32 xInt = ((int32) j) - (int32) fRadius + 1;
403 real64 xPos = xInt - xFract;
404
405 #if 0
406
407 // Radial.
408
409 real64 dy2 = yPos * yPos;
410 real64 dx2 = xPos * xPos;
411
412 real64 r = sqrt (dx2 + dy2);
413
414 w32 [index] = (real32) kernel.Evaluate (r);
415
416 #else
417
418 // Separable.
419
420 w32 [index] = (real32) kernel.Evaluate (xPos) *
421 (real32) kernel.Evaluate (yPos);
422
423 #endif
424
425 t32 += w32 [index];
426
427 index++;
428
429 }
430
431 }
432
433 // Scale 32 bit weights so total of weights is 1.0.
434
435 const real32 s32 = (real32) (1.0 / t32);
436
437 for (uint32 i = 0; i < widthSqr; i++)
438 {
439
440 w32 [i] *= s32;
441
442 }
443
444 }
445
446 // Round off 32 bit weights to 16 bit weights.
447
448 {
449
450 int16 *w16 = (int16 *) Weights16 (dng_point ((int32) y,
451 (int32) x));
452
453 int32 t16 = 0;
454
455 for (uint32 j = 0; j < widthSqr; j++)
456 {
457
458 w16 [j] = (int16) Round_int32 (w32 [j] * 16384.0);
459
460 t16 += w16 [j];
461
462 }
463
464 // Adjust one of the center entries for any round off error so total
465 // is exactly 16384.
466
467 const uint32 xOffset = fRadius - ((xFract >= 0.5) ? 0 : 1);
468 const uint32 yOffset = fRadius - ((yFract >= 0.5) ? 0 : 1);
469 const uint32 centerOffset = width * yOffset + xOffset;
470
471 w16 [centerOffset] += (int16) (16384 - t16);
472
473 }
474
475 }
476
477 }
478
479 }
480
481 /*****************************************************************************/
482
483 class dng_resample_task: public dng_filter_task
484 {
485
486 protected:
487
488 dng_rect fSrcBounds;
489 dng_rect fDstBounds;
490
491 const dng_resample_function &fKernel;
492
493 real64 fRowScale;
494 real64 fColScale;
495
496 dng_resample_coords fRowCoords;
497 dng_resample_coords fColCoords;
498
499 dng_resample_weights fWeightsV;
500 dng_resample_weights fWeightsH;
501
502 dng_point fSrcTileSize;
503
504 AutoPtr<dng_memory_block> fTempBuffer [kMaxMPThreads];
505
506 public:
507
508 dng_resample_task (const dng_image &srcImage,
509 dng_image &dstImage,
510 const dng_rect &srcBounds,
511 const dng_rect &dstBounds,
512 const dng_resample_function &kernel);
513
514 virtual dng_rect SrcArea (const dng_rect &dstArea);
515
516 virtual dng_point SrcTileSize (const dng_point &dstTileSize);
517
518 virtual void Start (uint32 threadCount,
519 const dng_point &tileSize,
520 dng_memory_allocator *allocator,
521 dng_abort_sniffer *sniffer);
522
523 virtual void ProcessArea (uint32 threadIndex,
524 dng_pixel_buffer &srcBuffer,
525 dng_pixel_buffer &dstBuffer);
526
527 };
528
529 /*****************************************************************************/
530
dng_resample_task(const dng_image & srcImage,dng_image & dstImage,const dng_rect & srcBounds,const dng_rect & dstBounds,const dng_resample_function & kernel)531 dng_resample_task::dng_resample_task (const dng_image &srcImage,
532 dng_image &dstImage,
533 const dng_rect &srcBounds,
534 const dng_rect &dstBounds,
535 const dng_resample_function &kernel)
536
537 : dng_filter_task (srcImage,
538 dstImage)
539
540 , fSrcBounds (srcBounds)
541 , fDstBounds (dstBounds)
542
543 , fKernel (kernel)
544
545 , fRowScale ((srcBounds.H () != 0) ? dstBounds.H () / (real64) srcBounds.H () : 0)
546 , fColScale ((srcBounds.W () != 0) ? dstBounds.W () / (real64) srcBounds.W () : 0)
547
548 , fRowCoords ()
549 , fColCoords ()
550
551 , fWeightsV ()
552 , fWeightsH ()
553
554 , fSrcTileSize ()
555
556 {
557 if (fRowScale == 0 || fColScale == 0)
558 {
559 ThrowBadFormat ();
560 }
561
562 if (srcImage.PixelSize () <= 2 &&
563 dstImage.PixelSize () <= 2 &&
564 srcImage.PixelRange () == dstImage.PixelRange ())
565 {
566 fSrcPixelType = ttShort;
567 fDstPixelType = ttShort;
568 }
569
570 else
571 {
572 fSrcPixelType = ttFloat;
573 fDstPixelType = ttFloat;
574 }
575
576 fUnitCell = dng_point (8, 8);
577
578 fMaxTileSize.v = Pin_int32 (fUnitCell.v,
579 Round_int32 (fMaxTileSize.v * fRowScale),
580 fMaxTileSize.v);
581
582 fMaxTileSize.h = Pin_int32 (fUnitCell.h,
583 Round_int32 (fMaxTileSize.h * fColScale),
584 fMaxTileSize.h);
585
586 }
587
588 /*****************************************************************************/
589
SrcArea(const dng_rect & dstArea)590 dng_rect dng_resample_task::SrcArea (const dng_rect &dstArea)
591 {
592
593 int32 offsetV = fWeightsV.Offset ();
594 int32 offsetH = fWeightsH.Offset ();
595
596 uint32 widthV = fWeightsV.Width ();
597 uint32 widthH = fWeightsH.Width ();
598
599 dng_rect srcArea;
600
601 srcArea.t = SafeInt32Add (fRowCoords.Pixel (dstArea.t), offsetV);
602 srcArea.l = SafeInt32Add (fColCoords.Pixel (dstArea.l), offsetH);
603
604 srcArea.b = SafeInt32Add (SafeInt32Add (
605 fRowCoords.Pixel (SafeInt32Sub (dstArea.b, 1)),
606 offsetV),
607 ConvertUint32ToInt32 (widthV));;
608 srcArea.r = SafeInt32Add(SafeInt32Add(
609 fColCoords.Pixel (SafeInt32Sub (dstArea.r, 1)),
610 offsetH),
611 ConvertUint32ToInt32(widthH));;
612
613 return srcArea;
614
615 }
616
617 /*****************************************************************************/
618
SrcTileSize(const dng_point &)619 dng_point dng_resample_task::SrcTileSize (const dng_point & /* dstTileSize */)
620 {
621
622 return fSrcTileSize;
623
624 }
625
626 /*****************************************************************************/
627
Start(uint32 threadCount,const dng_point & tileSize,dng_memory_allocator * allocator,dng_abort_sniffer * sniffer)628 void dng_resample_task::Start (uint32 threadCount,
629 const dng_point &tileSize,
630 dng_memory_allocator *allocator,
631 dng_abort_sniffer *sniffer)
632 {
633
634 // Compute sub-pixel resolution coordinates in the source image for
635 // each row and column of the destination area.
636
637 fRowCoords.Initialize (fSrcBounds.t,
638 fDstBounds.t,
639 fSrcBounds.H (),
640 fDstBounds.H (),
641 *allocator);
642
643 fColCoords.Initialize (fSrcBounds.l,
644 fDstBounds.l,
645 fSrcBounds.W (),
646 fDstBounds.W (),
647 *allocator);
648
649 // Compute resampling kernels.
650
651 fWeightsV.Initialize (fRowScale,
652 fKernel,
653 *allocator);
654
655 fWeightsH.Initialize (fColScale,
656 fKernel,
657 *allocator);
658
659 // Find upper bound on source source tile.
660
661 fSrcTileSize.v = Round_int32 (tileSize.v / fRowScale) + fWeightsV.Width () + 2;
662 fSrcTileSize.h = Round_int32 (tileSize.h / fColScale) + fWeightsH.Width () + 2;
663
664 // Allocate temp buffers.
665
666 uint32 tempBufferSize = 0;
667 if (!RoundUpUint32ToMultiple (fSrcTileSize.h, 8, &tempBufferSize) ||
668 !SafeUint32Mult (tempBufferSize,
669 static_cast<uint32> (sizeof (real32)),
670 &tempBufferSize))
671 {
672
673 ThrowMemoryFull("Arithmetic overflow computing buffer size.");
674
675 }
676
677 for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++)
678 {
679
680 fTempBuffer [threadIndex] . Reset (allocator->Allocate (tempBufferSize));
681
682 }
683
684 // Allocate the pixel buffers.
685
686 dng_filter_task::Start (threadCount,
687 tileSize,
688 allocator,
689 sniffer);
690
691 }
692
693 /*****************************************************************************/
694
ProcessArea(uint32 threadIndex,dng_pixel_buffer & srcBuffer,dng_pixel_buffer & dstBuffer)695 void dng_resample_task::ProcessArea (uint32 threadIndex,
696 dng_pixel_buffer &srcBuffer,
697 dng_pixel_buffer &dstBuffer)
698 {
699
700 dng_rect srcArea = srcBuffer.fArea;
701 dng_rect dstArea = dstBuffer.fArea;
702
703 uint32 srcCols = srcArea.W ();
704 uint32 dstCols = dstArea.W ();
705
706 uint32 widthV = fWeightsV.Width ();
707 uint32 widthH = fWeightsH.Width ();
708
709 int32 offsetV = fWeightsV.Offset ();
710 int32 offsetH = fWeightsH.Offset ();
711
712 uint32 stepH = fWeightsH.Step ();
713
714 const int32 *rowCoords = fRowCoords.Coords (0 );
715 const int32 *colCoords = fColCoords.Coords (dstArea.l);
716
717 if (fSrcPixelType == ttFloat)
718 {
719
720 const real32 *weightsH = fWeightsH.Weights32 (0);
721
722 real32 *tPtr = fTempBuffer [threadIndex]->Buffer_real32 ();
723
724 real32 *ttPtr = tPtr + offsetH - srcArea.l;
725
726 for (int32 dstRow = dstArea.t; dstRow < dstArea.b; dstRow++)
727 {
728
729 int32 rowCoord = rowCoords [dstRow];
730
731 int32 rowFract = rowCoord & kResampleSubsampleMask;
732
733 const real32 *weightsV = fWeightsV.Weights32 (rowFract);
734
735 int32 srcRow = (rowCoord >> kResampleSubsampleBits) + offsetV;
736
737 for (uint32 plane = 0; plane < dstBuffer.fPlanes; plane++)
738 {
739
740 const real32 *sPtr = srcBuffer.ConstPixel_real32 (srcRow,
741 srcArea.l,
742 plane);
743
744 DoResampleDown32 (sPtr,
745 tPtr,
746 srcCols,
747 srcBuffer.fRowStep,
748 weightsV,
749 widthV);
750
751 real32 *dPtr = dstBuffer.DirtyPixel_real32 (dstRow,
752 dstArea.l,
753 plane);
754
755 DoResampleAcross32 (ttPtr,
756 dPtr,
757 dstCols,
758 colCoords,
759 weightsH,
760 widthH,
761 stepH);
762
763 }
764
765 }
766
767 }
768
769 else
770 {
771
772 const int16 *weightsH = fWeightsH.Weights16 (0);
773
774 uint16 *tPtr = fTempBuffer [threadIndex]->Buffer_uint16 ();
775
776 uint16 *ttPtr = tPtr + offsetH - srcArea.l;
777
778 uint32 pixelRange = fDstImage.PixelRange ();
779
780 for (int32 dstRow = dstArea.t; dstRow < dstArea.b; dstRow++)
781 {
782
783 int32 rowCoord = rowCoords [dstRow];
784
785 int32 rowFract = rowCoord & kResampleSubsampleMask;
786
787 const int16 *weightsV = fWeightsV.Weights16 (rowFract);
788
789 int32 srcRow = (rowCoord >> kResampleSubsampleBits) + offsetV;
790
791 for (uint32 plane = 0; plane < dstBuffer.fPlanes; plane++)
792 {
793
794 const uint16 *sPtr = srcBuffer.ConstPixel_uint16 (srcRow,
795 srcArea.l,
796 plane);
797
798 DoResampleDown16 (sPtr,
799 tPtr,
800 srcCols,
801 srcBuffer.fRowStep,
802 weightsV,
803 widthV,
804 pixelRange);
805
806 uint16 *dPtr = dstBuffer.DirtyPixel_uint16 (dstRow,
807 dstArea.l,
808 plane);
809
810 DoResampleAcross16 (ttPtr,
811 dPtr,
812 dstCols,
813 colCoords,
814 weightsH,
815 widthH,
816 stepH,
817 pixelRange);
818
819 }
820
821 }
822
823 }
824
825 }
826
827 /*****************************************************************************/
828
ResampleImage(dng_host & host,const dng_image & srcImage,dng_image & dstImage,const dng_rect & srcBounds,const dng_rect & dstBounds,const dng_resample_function & kernel)829 void ResampleImage (dng_host &host,
830 const dng_image &srcImage,
831 dng_image &dstImage,
832 const dng_rect &srcBounds,
833 const dng_rect &dstBounds,
834 const dng_resample_function &kernel)
835 {
836
837 dng_resample_task task (srcImage,
838 dstImage,
839 srcBounds,
840 dstBounds,
841 kernel);
842
843 host.PerformAreaTask (task,
844 dstBounds);
845
846 }
847
848 /*****************************************************************************/
849