From d673d1126dda6bab847469fe1ffc776ac954f067 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 12 Oct 2018 12:36:55 +0100 Subject: [PATCH] Refactor Vector4Utils and ConvolutionProcessors utilizing them. --- .../Common/Helpers/DenseMatrixUtils.cs | 131 ++++++++++++++++++ src/ImageSharp/Common/Helpers/Vector4Utils.cs | 34 ++--- src/ImageSharp/Primitives/DenseMatrix{T}.cs | 10 +- .../Convolution/Convolution2DProcessor.cs | 90 +++--------- .../Convolution/Convolution2PassProcessor.cs | 56 +++----- .../Convolution/ConvolutionProcessor.cs | 75 +++------- .../Transforms/AffineTransformProcessor.cs | 7 +- .../ProjectiveTransformProcessor.cs | 7 +- .../General/Vectorization/Premultiply.cs | 59 ++++++++ .../Helpers/Vector4UtilsTests.cs | 4 +- 10 files changed, 279 insertions(+), 194 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs new file mode 100644 index 000000000..0755ae8a1 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -0,0 +1,131 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Primitives; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for . + /// + internal static class DenseMatrixUtils + { + /// + /// Computes the sum of vectors in weighted by the kernel weight values. + /// + /// The pixel format. + /// The dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve( + in DenseMatrix matrix, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vector = default; + int matrixHeight = matrix.Rows; + int matrixWidth = matrix.Columns; + int radiusY = matrixHeight >> 1; + int radiusX = matrixWidth >> 1; + + for (int y = 0; y < matrixHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixWidth; x++) + { + int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + currentColor *= matrix[y, x]; + vector += currentColor; + } + } + + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + + /// + /// Computes the sum of vectors in weighted by the two kernel weight values. + /// + /// The pixel format. + /// The vertical dense matrix. + /// The horizontal dense matrix. + /// The source frame. + /// The target row. + /// The current row. + /// The current column. + /// The maximum working area row. + /// The maximum working area column. + /// The column offset to apply to source sampling. + public static void Convolve2D( + in DenseMatrix matrixY, + in DenseMatrix matrixX, + Buffer2D sourcePixels, + Span targetRow, + int row, + int column, + int maxRow, + int maxColumn, + int offsetColumn) + where TPixel : struct, IPixel + { + Vector4 vectorY = default; + Vector4 vectorX = default; + int matrixYHeight = matrixY.Rows; + int matrixYWidth = matrixY.Columns; + int matrixXHeight = matrixX.Rows; + int matrixXWidth = matrixX.Columns; + int radiusY = matrixYHeight >> 1; + int radiusX = matrixXWidth >> 1; + + for (int y = 0; y < matrixYHeight; y++) + { + int offsetY = (row + y - radiusY).Clamp(0, maxRow); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + + for (int x = 0; x < matrixXWidth; x++) + { + int offsetX = (column + offsetColumn + x - radiusX).Clamp(offsetColumn, maxColumn); + var currentColor = sourceRowSpan[offsetX].ToVector4(); + Vector4Utils.Premultiply(ref currentColor); + + if (y < matrixXHeight) + { + vectorX += matrixX[y, x] * currentColor; + } + + if (x < matrixYWidth) + { + vectorY += matrixY[y, x] * currentColor; + } + } + } + + var vector = Vector4.SquareRoot((vectorX * vectorX) + (vectorY * vectorY)); + ref Vector4 target = ref targetRow[column]; + vector.W = target.W; + Vector4Utils.UnPremultiply(ref vector); + target = vector; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs index 4545d797c..75bb00b6a 100644 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs @@ -17,32 +17,28 @@ namespace SixLabors.ImageSharp /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. /// /// The to premultiply - /// The [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 Premultiply(Vector4 source) + public static void Premultiply(ref Vector4 source) { float w = source.W; - Vector4 premultiplied = source * w; - premultiplied.W = w; - return premultiplied; + source *= w; + source.W = w; } /// - /// Reverses the result of premultiplying a vector via . + /// Reverses the result of premultiplying a vector via . /// /// The to premultiply - /// The [MethodImpl(InliningOptions.ShortMethod)] - public static Vector4 UnPremultiply(Vector4 source) + public static void UnPremultiply(ref Vector4 source) { float w = source.W; - Vector4 unpremultiplied = source / w; - unpremultiplied.W = w; - return unpremultiplied; + source /= w; + source.W = w; } /// - /// Bulk variant of + /// Bulk variant of /// /// The span of vectors [MethodImpl(InliningOptions.ShortMethod)] @@ -54,16 +50,12 @@ namespace SixLabors.ImageSharp for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(v.W) - { - W = 1 - }; - v *= s; + Premultiply(ref v); } } /// - /// Bulk variant of + /// Bulk variant of /// /// The span of vectors [MethodImpl(InliningOptions.ShortMethod)] @@ -75,11 +67,7 @@ namespace SixLabors.ImageSharp for (int i = 0; i < vectors.Length; i++) { ref Vector4 v = ref Unsafe.Add(ref baseRef, i); - var s = new Vector4(1 / v.W) - { - W = 1 - }; - v *= s; + UnPremultiply(ref v); } } } diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index ef1abc897..2a4b9dc07 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -182,13 +182,13 @@ namespace SixLabors.ImageSharp.Primitives } /// - public bool Equals(DenseMatrix other) => - this.Columns == other.Columns && - this.Rows == other.Rows && - this.Span.SequenceEqual(other.Span); + public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public bool Equals(DenseMatrix other) => + this.Columns == other.Columns + && this.Rows == other.Rows + && this.Span.SequenceEqual(other.Span); /// public override int GetHashCode() => this.Data.GetHashCode(); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index c358b316c..1ecf9b759 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -3,8 +3,6 @@ using System; using System.Numerics; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; @@ -47,89 +45,43 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Rectangle sourceRectangle, Configuration configuration) { - int kernelYHeight = this.KernelY.Rows; - int kernelYWidth = this.KernelY.Columns; - int kernelXHeight = this.KernelX.Rows; - int kernelXWidth = this.KernelX.Columns; - int radiusY = kernelYHeight >> 1; - int radiusX = kernelXWidth >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrixY = this.KernelY; + DenseMatrix matrixX = this.KernelX; + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; - using (Buffer2D targetPixels = - configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) + using (Buffer2D targetPixels = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) { source.CopyTo(targetPixels); var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelHelper.IterateRowsWithTempBuffer( workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - float rX = 0; - float gX = 0; - float bX = 0; - float rY = 0; - float gY = 0; - float bY = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelYHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelXWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); - - if (fy < kernelXHeight) - { - Vector4 kx = this.KernelX[fy, fx] * currentColor; - rX += kx.X; - gX += kx.Y; - bX += kx.Z; - } - - if (fx < kernelYWidth) - { - Vector4 ky = this.KernelY[fy, fx] * currentColor; - rY += ky.X; - gY += ky.Y; - bY += ky.Z; - } - } - } - - float red = MathF.Sqrt((rX * rX) + (rY * rY)); - float green = MathF.Sqrt((gX * gX) + (gY * gY)); - float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4( - Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); + DenseMatrixUtils.Convolve2D(in matrixY, in matrixX, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 3135ff614..1f47649e6 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -45,8 +45,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { using (Buffer2D firstPassPixels = configuration.MemoryAllocator.Allocate2D(source.Size())) { - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, source.Bounds(), this.KernelX, configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, sourceRectangle, this.KernelY, configuration); + source.CopyTo(firstPassPixels); + + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, configuration); + this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, configuration); } } @@ -65,14 +68,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Buffer2D targetPixels, Buffer2D sourcePixels, Rectangle sourceRectangle, - DenseMatrix kernel, // TODO: Can't use 'in' as pass by ref to lambda expression. + in DenseMatrix kernel, Configuration configuration) { - int kernelHeight = kernel.Rows; - int kernelWidth = kernel.Columns; - int radiusY = kernelHeight >> 1; - int radiusX = kernelWidth >> 1; - + DenseMatrix matrix = kernel; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -81,44 +80,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int maxX = endX - 1; var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( + ParallelHelper.IterateRowsWithTempBuffer( workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - Vector4 destination = default; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelHeight; fy++) - { - int fyr = fy - radiusY; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span row = sourcePixels.GetRowSpan(offsetY); - - for (int fx = 0; fx < kernelWidth; fx++) - { - int fxr = fx - radiusX; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = Vector4Utils.Premultiply(row[offsetX].ToVector4()); - destination += kernel[fy, fx] * currentColor; - } - } - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4(Vector4Utils.UnPremultiply(destination)); + DenseMatrixUtils.Convolve(in matrix, sourcePixels, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 1d1755ccd..d2f3f8fc5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -3,14 +3,10 @@ using System; using System.Numerics; -using System.Threading.Tasks; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Primitives; -using SixLabors.ImageSharp.Processing.Processors; -using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing.Processors.Convolution @@ -26,10 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// Initializes a new instance of the class. /// /// The 2d gradient operator. - public ConvolutionProcessor(DenseMatrix kernelXY) - { - this.KernelXY = kernelXY; - } + public ConvolutionProcessor(DenseMatrix kernelXY) => this.KernelXY = kernelXY; /// /// Gets the 2d gradient operator. @@ -39,13 +32,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration) { - int kernelLength = this.KernelXY.Rows; - int radius = kernelLength >> 1; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; + DenseMatrix matrix = this.KernelXY; + var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); + int startY = interest.Y; + int endY = interest.Bottom; + int startX = interest.X; + int endX = interest.Right; int maxY = endY - 1; int maxX = endX - 1; @@ -53,53 +45,28 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { source.CopyTo(targetPixels); - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); + var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); + int width = workingRectangle.Width; - ParallelHelper.IterateRows( - workingRect, + ParallelHelper.IterateRowsWithTempBuffer( + workingRectangle, configuration, - rows => + (rows, vectorBuffer) => { + Span vectorSpan = vectorBuffer.Span; + int length = vectorSpan.Length; + for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = targetPixels.GetRowSpan(y); + Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + PixelOperations.Instance.ToVector4(targetRowSpan, vectorSpan, length); - for (int x = startX; x < endX; x++) + for (int x = 0; x < width; x++) { - float red = 0; - float green = 0; - float blue = 0; - - // Apply each matrix multiplier to the color components for each pixel. - for (int fy = 0; fy < kernelLength; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; - - offsetY = offsetY.Clamp(0, maxY); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); - - for (int fx = 0; fx < kernelLength; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - - offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = Vector4Utils.Premultiply(sourceOffsetRow[offsetX].ToVector4()); - currentColor *= this.KernelXY[fy, fx]; - - red += currentColor.X; - green += currentColor.Y; - blue += currentColor.Z; - } - } - - ref TPixel pixel = ref targetRow[x]; - pixel.PackFromVector4( - Vector4Utils.UnPremultiply(new Vector4(red, green, blue, sourceRow[x].ToVector4().W))); + DenseMatrixUtils.Convolve(in matrix, source.PixelBuffer, vectorSpan, y, x, maxY, maxX, startX); } + + PixelOperations.Instance.PackFromVector4(vectorSpan, targetRowSpan, length); } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs index 225c687d8..790eb8048 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs @@ -208,14 +208,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float xWeight = Unsafe.Add(ref xSpanRef, xx); // Values are first premultiplied to prevent darkening of edge pixels - sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs index f860264af..bad8eab3a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs @@ -217,14 +217,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float xWeight = Unsafe.Add(ref xSpanRef, xx); // Values are first premultiplied to prevent darkening of edge pixels - sum += Vector4Utils.Premultiply(source[i, j].ToVector4()) * xWeight * yWeight; + var current = source[i, j].ToVector4(); + Vector4Utils.Premultiply(ref current); + sum += current * xWeight * yWeight; } } ref TPixel dest = ref Unsafe.Add(ref destRowRef, x); // Reverse the premultiplication - dest.PackFromVector4(Vector4Utils.UnPremultiply(sum)); + Vector4Utils.UnPremultiply(ref sum); + dest.PackFromVector4(sum); } } }); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs new file mode 100644 index 000000000..23f13c89b --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization +{ + public class Premultiply + { + [Benchmark(Baseline = true)] + public Vector4 PremultiplyByVal() + { + var input = new Vector4(.5F); + return Vector4Utils.Premultiply(input); + } + + [Benchmark] + public Vector4 PremultiplyByRef() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRef(ref input); + return input; + } + + [Benchmark] + public Vector4 PremultiplyRefWithPropertyAssign() + { + var input = new Vector4(.5F); + Vector4Utils.PremultiplyRefWithPropertyAssign(ref input); + return input; + } + } + + internal static class Vector4Utils + { + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 Premultiply(Vector4 source) + { + float w = source.W; + Vector4 premultiplied = source * w; + premultiplied.W = w; + return premultiplied; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRef(ref Vector4 source) + { + float w = source.W; + source *= w; + source.W = w; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public static void PremultiplyRefWithPropertyAssign(ref Vector4 source) + { + float w = source.W; + source *= new Vector4(w) { W = 1 }; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index d2e1ddf4e..9416be740 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => Vector4Utils.Premultiply(v)).ToArray(); + Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); Vector4Utils.Premultiply(source); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => Vector4Utils.UnPremultiply(v)).ToArray(); + Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); Vector4Utils.UnPremultiply(source);