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);