diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index afce39dcd..def189753 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -268,17 +268,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution protected override void OnFrameApply(ImageFrame source) { // Preliminary gamma highlight pass - this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration); + ParallelRowIterator.IterateRows( + this.SourceRectangle, + this.Configuration, + new ApplyGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma)); // Create a 0-filled buffer to use to store the result of the component convolutions - using (Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean)) - { - // Perform the 1D convolutions on all the kernel components and accumulate the results - this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); + using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); - // Apply the inverse gamma exposure pass, and write the final pixel data - this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration); - } + // Perform the 1D convolutions on all the kernel components and accumulate the results + this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); + + float inverseGamma = 1 / this.gamma; + + // Apply the inverse gamma exposure pass, and write the final pixel data + ParallelRowIterator.IterateRows( + this.SourceRectangle, + this.Configuration, + new ApplyInverseGammaExposureRowIntervalAction(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma)); } /// @@ -294,216 +301,253 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Configuration configuration, Buffer2D processingBuffer) { - using (Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) + // Allocate the buffer with the intermediate convolution results + using Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + // Perform two 1D convolutions for each component in the current instance + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + for (int i = 0; i < this.kernels.Length; i++) { - // Perform two 1D convolutions for each component in the current instance - ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); - ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - for (int i = 0; i < this.kernels.Length; i++) - { - // Compute the resulting complex buffer for the current component - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - Complex64[] kernel = Unsafe.Add(ref baseRef, i); - Vector4 parameters = Unsafe.Add(ref paramsRef, i); - - // Compute the two 1D convolutions and accumulate the partial results on the target buffer - this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); - this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W); - } + // Compute the resulting complex buffer for the current component + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Compute the vertical 1D convolution + ParallelRowIterator.IterateRows( + sourceRectangle, + configuration, + new ApplyVerticalConvolutionRowIntervalAction(ref sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel)); + + // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + ParallelRowIterator.IterateRows( + sourceRectangle, + configuration, + new ApplyHorizontalConvolutionRowIntervalAction(ref sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W)); } } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the vertical convolution logic for . /// - /// The target values to use to store the results. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 1D kernel. - /// The - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration) + private readonly struct ApplyVerticalConvolutionRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target values to use to store the results. + /// The source pixels. Cannot be null. + /// The 1D kernel. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowIntervalAction( + ref Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + Complex64[] kernel) + { + this.bounds = bounds; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX); } - }); + } + } } /// - /// Applies the process to the specified portion of the specified buffer at the specified location - /// and with the specified size. + /// A implementing the horizontal convolution logic for . /// - /// The target values to use to store the results. - /// The source complex values. Cannot be null. - /// The structure that specifies the portion of the image object to draw. - /// The 1D kernel. - /// The - /// The weight factor for the real component of the complex pixel values. - /// The weight factor for the imaginary component of the complex pixel values. - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration, - float z, - float w) + private readonly struct ApplyHorizontalConvolutionRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target values to use to store the results. + /// The source complex values. Cannot be null. + /// The 1D kernel. + /// The weight factor for the real component of the complex pixel values. + /// The weight factor for the imaginary component of the complex pixel values. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowIntervalAction( + ref Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + float w) + { + this.bounds = bounds; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.kernel = kernel; + this.z = z; + this.w = w; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w); - } + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, maxY, this.bounds.X, maxX, this.z, this.w); } - }); + } + } } /// - /// Applies the gamma correction/highlight to the input pixel buffer. + /// A implementing the convolution logic for . /// - /// The target pixel buffer to adjust. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyGammaExposure( - Buffer2D targetPixels, - Rectangle sourceRectangle, - Configuration configuration) + private readonly struct ApplyGammaExposureRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float exp = this.gamma; - - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - (rows, vectorBuffer) => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixel buffer to adjust. + /// The + /// The gamma parameter to use. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration, + float gamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + this.gamma = gamma; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows, Memory memory) + { + Span vectorSpan = memory.Span; + int length = vectorSpan.Length; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, exp); - v.Y = MathF.Pow(v.Y, exp); - v.Z = MathF.Pow(v.Z, exp); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); - } - }); + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorSpan.Slice(0, length), targetRowSpan); + } + } } /// - /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. + /// A implementing the convolution logic for . /// - /// The target pixels to apply the process to. - /// The source values. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyInverseGammaExposure( - Buffer2D targetPixels, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Configuration configuration) + private readonly struct ApplyInverseGammaExposureRowIntervalAction : IRowIntervalAction { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + private readonly float inverseGamma; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The target pixels to apply the process to. + /// The source values. Cannot be null. + /// The + /// The inverse gamma parameter to use. + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGammaExposureRowIntervalAction( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourceValues, + Configuration configuration, + float inverseGamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + this.inverseGamma = inverseGamma; + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float expGamma = 1 / this.gamma; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - ParallelRowIterator.IterateRows( - workingRectangle, - configuration, - rows => + for (int y = rows.Min; y < rows.Max; y++) + { + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + + for (int x = 0; x < this.bounds.Width; x++) { - Vector4 low = Vector4.Zero; - var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, expGamma); - v.Y = MathF.Pow(clamp.Y, expGamma); - v.Z = MathF.Pow(clamp.Z, expGamma); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply); - } - }); + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4.Clamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } + } } } }