From 2ed67f0562c5ecae231e22fdaeda2776473001d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 24 May 2017 13:43:28 +1000 Subject: [PATCH] Optimize Convolution processors --- src/ImageSharp/Image/ImageBase{TPixel}.cs | 9 +++++++ src/ImageSharp/Image/PixelAccessor{TPixel}.cs | 26 ++++++++++--------- src/ImageSharp/Memory/SpanHelper.cs | 2 +- .../Convolution/Convolution2DProcessor.cs | 18 +++++++------ .../Convolution/Convolution2PassProcessor.cs | 14 +++++----- .../Convolution/ConvolutionProcessor.cs | 16 +++++++----- 6 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index eb464c74e..60fcdda48 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -231,6 +231,15 @@ namespace ImageSharp return new PixelAccessor(this); } + /// + /// Copies the pixels to another of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(PixelAccessor target) + { + SpanHelper.Copy(this.span, target.PixelBuffer.Span); + } + /// /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. /// diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 240e65c82..1d776c258 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -20,6 +20,13 @@ namespace ImageSharp internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { +#pragma warning disable SA1401 // Fields must be private + /// + /// The containing the pixel data. + /// + internal Buffer2D PixelBuffer; +#pragma warning restore SA1401 // Fields must be private + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -31,11 +38,6 @@ namespace ImageSharp /// private bool isDisposed; - /// - /// The containing the pixel data. - /// - private Buffer2D pixelBuffer; - /// /// Initializes a new instance of the class. /// @@ -88,7 +90,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TPixel[] PixelArray => this.pixelBuffer.Array; + public TPixel[] PixelArray => this.PixelBuffer.Array; /// /// Gets the size of a single pixel in the number of bytes. @@ -116,7 +118,7 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } /// - Span IBuffer2D.Span => this.pixelBuffer; + Span IBuffer2D.Span => this.PixelBuffer; private static PixelOperations Operations => PixelOperations.Instance; @@ -156,7 +158,7 @@ namespace ImageSharp // Note disposing is done. this.isDisposed = true; - this.pixelBuffer.Dispose(); + this.PixelBuffer.Dispose(); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to @@ -171,7 +173,7 @@ namespace ImageSharp /// public void Reset() { - this.pixelBuffer.Clear(); + this.PixelBuffer.Clear(); } /// @@ -243,7 +245,7 @@ namespace ImageSharp /// If is true then caller is responsible for ensuring is called. internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels) { - TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); + TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership(); this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -254,7 +256,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); + SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } /// @@ -425,7 +427,7 @@ namespace ImageSharp /// The pixel buffer private void SetPixelBufferUnsafe(int width, int height, Buffer2D pixels) { - this.pixelBuffer = pixels; + this.PixelBuffer = pixels; this.Width = width; this.Height = height; diff --git a/src/ImageSharp/Memory/SpanHelper.cs b/src/ImageSharp/Memory/SpanHelper.cs index 57b771591..0e794e1b5 100644 --- a/src/ImageSharp/Memory/SpanHelper.cs +++ b/src/ImageSharp/Memory/SpanHelper.cs @@ -70,7 +70,7 @@ namespace ImageSharp.Memory public static void Copy(Span source, Span destination) where T : struct { - Copy(source, destination, source.Length); + Copy(source, destination, Math.Min(source.Length, destination.Length)); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 29086b53f..b6b56adb3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Processing.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float rX = 0; @@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { @@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); if (fy < kernelXHeight) { @@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e39104793..efc00b08f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (var firstPassPixels = new PixelAccessor(width, height)) using (PixelAccessor sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds, this.KernelX); @@ -84,9 +84,11 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { - Vector4 destination = default(Vector4); + var destination = default(Vector4); // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelHeight; fy++) @@ -95,6 +97,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span row = sourcePixels.GetRowSpan(offsetY); for (int fx = 0; fx < kernelWidth; fx++) { @@ -103,14 +106,13 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = row[offsetX].ToVector4(); destination += kernel[fy, fx] * currentColor; } } - TPixel packed = default(TPixel); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d5e3243..06607c87a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -46,10 +46,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -57,6 +56,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float red = 0; @@ -70,6 +72,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { @@ -78,7 +81,7 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -87,9 +90,8 @@ namespace ImageSharp.Processing.Processors } } - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } });