|
|
|
@ -3,6 +3,7 @@ |
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Numerics; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using SixLabors.ImageSharp.Advanced; |
|
|
|
using SixLabors.ImageSharp.Memory; |
|
|
|
@ -58,95 +59,113 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution |
|
|
|
/// <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()); |
|
|
|
this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration); |
|
|
|
this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration); |
|
|
|
} |
|
|
|
using Buffer2D<TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D<TPixel>(source.Size()); |
|
|
|
|
|
|
|
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); |
|
|
|
|
|
|
|
// Horizontal convolution
|
|
|
|
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>( |
|
|
|
interest, |
|
|
|
this.Configuration, |
|
|
|
new RowIntervalAction(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha)); |
|
|
|
|
|
|
|
// Vertical convolution
|
|
|
|
ParallelRowIterator.IterateRows<RowIntervalAction, Vector4>( |
|
|
|
interest, |
|
|
|
this.Configuration, |
|
|
|
new RowIntervalAction(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/> at the specified location
|
|
|
|
/// and with the specified size.
|
|
|
|
/// A <see langword="struct"/> implementing the convolution logic for <see cref="Convolution2PassProcessor{T}"/>.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="targetPixels">The target pixels to apply the process to.</param>
|
|
|
|
/// <param name="sourcePixels">The source pixels. 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="kernel">The kernel operator.</param>
|
|
|
|
/// <param name="configuration">The <see cref="Configuration"/></param>
|
|
|
|
private void ApplyConvolution( |
|
|
|
Buffer2D<TPixel> targetPixels, |
|
|
|
Buffer2D<TPixel> sourcePixels, |
|
|
|
Rectangle sourceRectangle, |
|
|
|
in DenseMatrix<float> kernel, |
|
|
|
Configuration configuration) |
|
|
|
private readonly struct RowIntervalAction : IRowIntervalAction<Vector4> |
|
|
|
{ |
|
|
|
DenseMatrix<float> matrix = kernel; |
|
|
|
bool preserveAlpha = this.PreserveAlpha; |
|
|
|
|
|
|
|
int startY = sourceRectangle.Y; |
|
|
|
int endY = sourceRectangle.Bottom; |
|
|
|
int startX = sourceRectangle.X; |
|
|
|
int endX = sourceRectangle.Right; |
|
|
|
int maxY = endY - 1; |
|
|
|
int maxX = endX - 1; |
|
|
|
|
|
|
|
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); |
|
|
|
int width = workingRectangle.Width; |
|
|
|
|
|
|
|
ParallelRowIterator.IterateRows<Vector4>( |
|
|
|
workingRectangle, |
|
|
|
configuration, |
|
|
|
(rows, vectorBuffer) => |
|
|
|
{ |
|
|
|
Span<Vector4> vectorSpan = vectorBuffer.Span; |
|
|
|
int length = vectorSpan.Length; |
|
|
|
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); |
|
|
|
private readonly Rectangle bounds; |
|
|
|
private readonly Buffer2D<TPixel> targetPixels; |
|
|
|
private readonly Buffer2D<TPixel> sourcePixels; |
|
|
|
private readonly DenseMatrix<float> kernel; |
|
|
|
private readonly Configuration configuration; |
|
|
|
private readonly bool preserveAlpha; |
|
|
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="RowIntervalAction"/> struct.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="bounds">The target processing bounds for the current instance.</param>
|
|
|
|
/// <param name="targetPixels">The target pixel buffer to adjust.</param>
|
|
|
|
/// <param name="sourcePixels">The source pixels. Cannot be null.</param>
|
|
|
|
/// <param name="kernel">The kernel operator.</param>
|
|
|
|
/// <param name="configuration">The <see cref="Configuration"/></param>
|
|
|
|
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public RowIntervalAction( |
|
|
|
Rectangle bounds, |
|
|
|
Buffer2D<TPixel> targetPixels, |
|
|
|
Buffer2D<TPixel> sourcePixels, |
|
|
|
DenseMatrix<float> kernel, |
|
|
|
Configuration configuration, |
|
|
|
bool preserveAlpha) |
|
|
|
{ |
|
|
|
this.bounds = bounds; |
|
|
|
this.targetPixels = targetPixels; |
|
|
|
this.sourcePixels = sourcePixels; |
|
|
|
this.kernel = kernel; |
|
|
|
this.configuration = configuration; |
|
|
|
this.preserveAlpha = preserveAlpha; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
[MethodImpl(InliningOptions.ShortMethod)] |
|
|
|
public void Invoke(in RowInterval rows, Memory<Vector4> memory) |
|
|
|
{ |
|
|
|
Span<Vector4> vectorSpan = memory.Span; |
|
|
|
int length = vectorSpan.Length; |
|
|
|
ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); |
|
|
|
|
|
|
|
int maxY = this.bounds.Bottom - 1; |
|
|
|
int maxX = this.bounds.Right - 1; |
|
|
|
|
|
|
|
for (int y = rows.Min; y < rows.Max; y++) |
|
|
|
{ |
|
|
|
Span<TPixel> targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); |
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, length), vectorSpan); |
|
|
|
|
|
|
|
if (this.preserveAlpha) |
|
|
|
{ |
|
|
|
for (int x = 0; x < this.bounds.Width; x++) |
|
|
|
{ |
|
|
|
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); |
|
|
|
PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); |
|
|
|
|
|
|
|
if (preserveAlpha) |
|
|
|
{ |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
DenseMatrixUtils.Convolve3( |
|
|
|
in matrix, |
|
|
|
sourcePixels, |
|
|
|
ref vectorSpanRef, |
|
|
|
y, |
|
|
|
x, |
|
|
|
startY, |
|
|
|
maxY, |
|
|
|
startX, |
|
|
|
maxX); |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < width; x++) |
|
|
|
{ |
|
|
|
DenseMatrixUtils.Convolve4( |
|
|
|
in matrix, |
|
|
|
sourcePixels, |
|
|
|
ref vectorSpanRef, |
|
|
|
y, |
|
|
|
x, |
|
|
|
startY, |
|
|
|
maxY, |
|
|
|
startX, |
|
|
|
maxX); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); |
|
|
|
DenseMatrixUtils.Convolve3( |
|
|
|
in this.kernel, |
|
|
|
this.sourcePixels, |
|
|
|
ref vectorSpanRef, |
|
|
|
y, |
|
|
|
x, |
|
|
|
this.bounds.Y, |
|
|
|
maxY, |
|
|
|
this.bounds.X, |
|
|
|
maxX); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
for (int x = 0; x < this.bounds.Width; x++) |
|
|
|
{ |
|
|
|
DenseMatrixUtils.Convolve4( |
|
|
|
in this.kernel, |
|
|
|
this.sourcePixels, |
|
|
|
ref vectorSpanRef, |
|
|
|
y, |
|
|
|
x, |
|
|
|
this.bounds.Y, |
|
|
|
maxY, |
|
|
|
this.bounds.X, |
|
|
|
maxX); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorSpan, targetRowSpan); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|