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; + } + } + } +}