/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { using Buffer2D <TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We use a rectangle 2x the interest width to allocate a buffer big enough // for source and target bulk pixel conversion. var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); // We can create a single sampling map with the size as if we were using the non separated 2D kernel // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest); // Horizontal convolution var horizontalOperation = new HorizontalConvolutionRowOperation( interest, firstPassPixels, source.PixelBuffer, mapXY, this.Kernel, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows <HorizontalConvolutionRowOperation, Vector4>( this.Configuration, operationBounds, in horizontalOperation); // Vertical convolution var verticalOperation = new VerticalConvolutionRowOperation( interest, source.PixelBuffer, firstPassPixels, mapXY, this.Kernel, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows <VerticalConvolutionRowOperation, Vector4>( this.Configuration, operationBounds, in verticalOperation); }
/// <summary> /// Computes and aggregates the convolution for each complex kernel component in the processor. /// </summary> /// <param name="source">The source image. Cannot be null.</param> /// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param> /// <param name="configuration">The configuration.</param> /// <param name="processingBuffer">The buffer with the raw pixel data to use to aggregate the results of each convolution.</param> private void OnFrameApplyCore( ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration, Buffer2D <Vector4> processingBuffer) { // Allocate the buffer with the intermediate convolution results using Buffer2D <ComplexVector4> firstPassBuffer = configuration.MemoryAllocator.Allocate2D <ComplexVector4>(source.Size()); // Unlike in the standard 2 pass convolution processor, we use a rectangle of 1x the interest width // to speedup the actual convolution, by applying bulk pixel conversion and clamping calculation. // The second half of the buffer will just target the temporary buffer of complex pixel values. // This is needed because the bokeh blur operates as TPixel -> complex -> TPixel, so we cannot // convert back to standard pixels after each separate 1D convolution pass. Like in the gaussian // blur though, we preallocate and compute the kernel sampling maps before processing each complex // component, to avoid recomputing the same sampling map once per convolution pass. Since we are // doing two 1D convolutions with the same kernel, we can use a single kernel sampling map as if // we were using a 2D kernel with each dimension being the same as the length of our kernel, and // use the two sampling offset spans resulting from this same map. This saves some extra work. using var mapXY = new KernelSamplingMap(configuration.MemoryAllocator); mapXY.BuildSamplingOffsetMap(this.kernelSize, this.kernelSize, sourceRectangle); ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan());