diff --git a/.gitattributes b/.gitattributes
index c0bff6e189..7c648c0774 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -80,8 +80,11 @@
*.pvr binary
*.snk binary
*.tga binary
+*.tif binary
+*.tiff binary
*.ttc binary
*.ttf binary
+*.wbmp binary
*.webp binary
*.woff binary
*.woff2 binary
diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
index f265bdd517..cf7eb1162a 100644
--- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
+++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
@@ -6,6 +6,7 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Convolution;
namespace SixLabors.ImageSharp
{
@@ -156,38 +157,32 @@ namespace SixLabors.ImageSharp
///
/// The pixel format.
/// The dense matrix.
+ /// The span containing precalculated kernel y-offsets.
+ /// The span containing precalculated kernel x-offsets.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- /// The minimum working area row.
- /// The maximum working area row.
- /// The minimum working area column.
- /// The maximum working area column.
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve3(
in DenseMatrix matrix,
+ Span yOffsetSpan,
+ Span xOffsetSpan,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
- int column,
- int minRow,
- int maxRow,
- int minColumn,
- int maxColumn)
+ int column)
where TPixel : unmanaged, IPixel
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
+ yOffsetSpan,
+ xOffsetSpan,
sourcePixels,
row,
column,
- minRow,
- maxRow,
- minColumn,
- maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
@@ -203,38 +198,32 @@ namespace SixLabors.ImageSharp
///
/// The pixel format.
/// The dense matrix.
+ /// The span containing precalculated kernel y-offsets.
+ /// The span containing precalculated kernel x-offsets.
/// The source frame.
/// The target row base reference.
/// The current row.
/// The current column.
- /// The minimum working area row.
- /// The maximum working area row.
- /// The minimum working area column.
- /// The maximum working area column.
[MethodImpl(InliningOptions.ShortMethod)]
public static void Convolve4(
in DenseMatrix matrix,
+ Span yOffsetSpan,
+ Span xOffsetSpan,
Buffer2D sourcePixels,
ref Vector4 targetRowRef,
int row,
- int column,
- int minRow,
- int maxRow,
- int minColumn,
- int maxColumn)
+ int column)
where TPixel : unmanaged, IPixel
{
Vector4 vector = default;
ConvolveImpl(
in matrix,
+ yOffsetSpan,
+ xOffsetSpan,
sourcePixels,
row,
column,
- minRow,
- maxRow,
- minColumn,
- maxColumn,
ref vector);
ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column);
@@ -245,33 +234,28 @@ namespace SixLabors.ImageSharp
[MethodImpl(InliningOptions.ShortMethod)]
private static void ConvolveImpl(
in DenseMatrix matrix,
+ Span yOffsetSpan,
+ Span xOffsetSpan,
Buffer2D sourcePixels,
int row,
int column,
- int minRow,
- int maxRow,
- int minColumn,
- int maxColumn,
- ref Vector4 vector)
+ ref Vector4 targetVector)
where TPixel : unmanaged, IPixel
{
int matrixHeight = matrix.Rows;
int matrixWidth = matrix.Columns;
- int radiusY = matrixHeight >> 1;
- int radiusX = matrixWidth >> 1;
- int sourceOffsetColumnBase = column + minColumn;
for (int y = 0; y < matrixHeight; y++)
{
- int offsetY = Numerics.Clamp(row + y - radiusY, minRow, maxRow);
+ int offsetY = yOffsetSpan[(row * matrixHeight) + y];
Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY);
for (int x = 0; x < matrixWidth; x++)
{
- int offsetX = Numerics.Clamp(sourceOffsetColumnBase + x - radiusX, minColumn, maxColumn);
+ int offsetX = xOffsetSpan[(column * matrixWidth) + x];
var currentColor = sourceRowSpan[offsetX].ToVector4();
Numerics.Premultiply(ref currentColor);
- vector += matrix[y, x] * currentColor;
+ targetVector += matrix[y, x] * currentColor;
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
index b61690415a..bc17378c88 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
@@ -63,19 +63,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- // Horizontal convolution
- var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha);
- ParallelRowIterator.IterateRows(
- this.Configuration,
- interest,
- in horizontalOperation);
-
- // Vertical convolution
- var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha);
- ParallelRowIterator.IterateRows(
- this.Configuration,
- interest,
- in verticalOperation);
+ using (var mapX = new KernelOffsetMap(this.Configuration.MemoryAllocator))
+ {
+ mapX.BuildOffsetMap(this.KernelX, interest);
+
+ // Horizontal convolution
+ var horizontalOperation = new RowOperation(
+ interest,
+ firstPassPixels,
+ source.PixelBuffer,
+ mapX,
+ this.KernelX,
+ this.Configuration,
+ this.PreserveAlpha);
+
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in horizontalOperation);
+ }
+
+ using (var mapY = new KernelOffsetMap(this.Configuration.MemoryAllocator))
+ {
+ mapY.BuildOffsetMap(this.KernelY, interest);
+
+ // Vertical convolution
+ var verticalOperation = new RowOperation(
+ interest,
+ source.PixelBuffer,
+ firstPassPixels,
+ mapY,
+ this.KernelY,
+ this.Configuration,
+ this.PreserveAlpha);
+
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in verticalOperation);
+ }
}
///
@@ -86,6 +112,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly Rectangle bounds;
private readonly Buffer2D targetPixels;
private readonly Buffer2D sourcePixels;
+ private readonly KernelOffsetMap map;
private readonly DenseMatrix kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@@ -95,6 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Rectangle bounds,
Buffer2D targetPixels,
Buffer2D sourcePixels,
+ KernelOffsetMap map,
DenseMatrix kernel,
Configuration configuration,
bool preserveAlpha)
@@ -102,6 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.bounds = bounds;
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
+ this.map = map;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
@@ -112,43 +141,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public void Invoke(int y, Span span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
-
- int maxY = this.bounds.Bottom - 1;
- int maxX = this.bounds.Right - 1;
-
Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
+ Span yOffsets = this.map.GetYOffsetSpan();
+ Span xOffsets = this.map.GetXOffsetSpan();
+ int row = y - this.bounds.Y;
if (this.preserveAlpha)
{
- for (int x = 0; x < this.bounds.Width; x++)
+ for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
+ yOffsets,
+ xOffsets,
this.sourcePixels,
ref spanRef,
- y,
- x,
- this.bounds.Y,
- maxY,
- this.bounds.X,
- maxX);
+ row,
+ column);
}
}
else
{
- for (int x = 0; x < this.bounds.Width; x++)
+ for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
+ yOffsets,
+ xOffsets,
this.sourcePixels,
ref spanRef,
- y,
- x,
- this.bounds.Y,
- maxY,
- this.bounds.X,
- maxX);
+ row,
+ column);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
index 95fef15f62..b2c5de396f 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs
@@ -51,16 +51,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
protected override void OnFrameApply(ImageFrame source)
{
- using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size());
+ MemoryAllocator allocator = this.Configuration.MemoryAllocator;
+ using Buffer2D targetPixels = allocator.Allocate2D(source.Size());
source.CopyTo(targetPixels);
var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
- var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha);
- ParallelRowIterator.IterateRows(
- this.Configuration,
- interest,
- in operation);
+ using (var map = new KernelOffsetMap(allocator))
+ {
+ map.BuildOffsetMap(this.KernelXY, interest);
+
+ var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, map, this.KernelXY, this.Configuration, this.PreserveAlpha);
+ ParallelRowIterator.IterateRows(
+ this.Configuration,
+ interest,
+ in operation);
+ }
Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels);
}
@@ -71,10 +77,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
private readonly struct RowOperation : IRowOperation
{
private readonly Rectangle bounds;
- private readonly int maxY;
- private readonly int maxX;
private readonly Buffer2D targetPixels;
private readonly Buffer2D sourcePixels;
+ private readonly KernelOffsetMap map;
private readonly DenseMatrix kernel;
private readonly Configuration configuration;
private readonly bool preserveAlpha;
@@ -84,15 +89,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
Rectangle bounds,
Buffer2D targetPixels,
Buffer2D sourcePixels,
+ KernelOffsetMap map,
DenseMatrix kernel,
Configuration configuration,
bool preserveAlpha)
{
this.bounds = bounds;
- this.maxY = this.bounds.Bottom - 1;
- this.maxX = this.bounds.Right - 1;
this.targetPixels = targetPixels;
this.sourcePixels = sourcePixels;
+ this.map = map;
this.kernel = kernel;
this.configuration = configuration;
this.preserveAlpha = preserveAlpha;
@@ -103,40 +108,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
public void Invoke(int y, Span span)
{
ref Vector4 spanRef = ref MemoryMarshal.GetReference(span);
-
Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X);
PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span);
+ Span yOffsetSpan = this.map.GetYOffsetSpan();
+ Span xOffsetSpan = this.map.GetXOffsetSpan();
+ int row = y - this.bounds.Y;
if (this.preserveAlpha)
{
- for (int x = 0; x < this.bounds.Width; x++)
+ for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve3(
in this.kernel,
+ yOffsetSpan,
+ xOffsetSpan,
this.sourcePixels,
ref spanRef,
- y,
- x,
- this.bounds.Y,
- this.maxY,
- this.bounds.X,
- this.maxX);
+ row,
+ column);
}
}
else
{
- for (int x = 0; x < this.bounds.Width; x++)
+ for (int column = 0; column < this.bounds.Width; column++)
{
DenseMatrixUtils.Convolve4(
in this.kernel,
+ yOffsetSpan,
+ xOffsetSpan,
this.sourcePixels,
ref spanRef,
- y,
- x,
- this.bounds.Y,
- this.maxY,
- this.bounds.X,
- this.maxX);
+ row,
+ column);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs
new file mode 100644
index 0000000000..c1adf357ca
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Convolution/Kernels/KernelOffsetMap.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Convolution
+{
+ ///
+ /// Provides a map of the convolution kernel sampling offsets.
+ ///
+ internal sealed class KernelOffsetMap : IDisposable
+ {
+ private readonly MemoryAllocator allocator;
+ private bool isDisposed;
+ private IMemoryOwner yOffsets;
+ private IMemoryOwner xOffsets;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator.
+ public KernelOffsetMap(MemoryAllocator allocator) => this.allocator = allocator;
+
+ public void BuildOffsetMap(in DenseMatrix matrix, Rectangle bounds)
+ {
+ int matrixHeight = matrix.Rows;
+ int matrixWidth = matrix.Columns;
+ this.yOffsets = this.allocator.Allocate(bounds.Height * matrixHeight);
+ this.xOffsets = this.allocator.Allocate(bounds.Width * matrixWidth);
+
+ int minY = bounds.Y;
+ int maxY = bounds.Bottom - 1;
+ int minX = bounds.X;
+ int maxX = bounds.Right - 1;
+
+ int radiusY = matrixHeight >> 1;
+ int radiusX = matrixWidth >> 1;
+
+ // Calculate the potential sampling y-offsets.
+ Span ySpan = this.yOffsets.GetSpan();
+ for (int row = 0; row < bounds.Height; row++)
+ {
+ for (int y = 0; y < matrixHeight; y++)
+ {
+ ySpan[(row * matrixHeight) + y] = row + y + minY - radiusY;
+ }
+ }
+
+ if (matrixHeight > 1)
+ {
+ Numerics.Clamp(ySpan, minY, maxY);
+ }
+
+ // Calculate the potential sampling x-offsets.
+ Span xSpan = this.xOffsets.GetSpan();
+ for (int column = 0; column < bounds.Width; column++)
+ {
+ for (int x = 0; x < matrixWidth; x++)
+ {
+ xSpan[(column * matrixWidth) + x] = column + x + minX - radiusX;
+ }
+ }
+
+ if (matrixWidth > 1)
+ {
+ Numerics.Clamp(xSpan, minX, maxX);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetYOffsetSpan() => this.yOffsets.GetSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetXOffsetSpan() => this.xOffsets.GetSpan();
+
+ ///
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.yOffsets.Dispose();
+ this.xOffsets.Dispose();
+
+ this.isDisposed = true;
+ }
+ }
+ }
+}