Browse Source

Refactor Vector4Utils and ConvolutionProcessors utilizing them.

af/merge-core
James Jackson-South 7 years ago
parent
commit
400a3cbe89
  1. 131
      src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs
  2. 34
      src/ImageSharp/Common/Helpers/Vector4Utils.cs
  3. 10
      src/ImageSharp/Primitives/DenseMatrix{T}.cs
  4. 90
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  5. 56
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  6. 75
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  7. 7
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  8. 7
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  9. 59
      tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs
  10. 4
      tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs

131
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
{
/// <summary>
/// Extension methods for <see cref="DenseMatrix{T}"/>.
/// </summary>
internal static class DenseMatrixUtils
{
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the kernel weight values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrix">The dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve<TPixel>(
in DenseMatrix<float> matrix,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int row,
int column,
int maxRow,
int maxColumn,
int offsetColumn)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel> 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;
}
/// <summary>
/// Computes the sum of vectors in <paramref name="targetRow"/> weighted by the two kernel weight values.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="matrixY">The vertical dense matrix.</param>
/// <param name="matrixX">The horizontal dense matrix.</param>
/// <param name="sourcePixels">The source frame.</param>
/// <param name="targetRow">The target row.</param>
/// <param name="row">The current row.</param>
/// <param name="column">The current column.</param>
/// <param name="maxRow">The maximum working area row.</param>
/// <param name="maxColumn">The maximum working area column.</param>
/// <param name="offsetColumn">The column offset to apply to source sampling.</param>
public static void Convolve2D<TPixel>(
in DenseMatrix<float> matrixY,
in DenseMatrix<float> matrixX,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int row,
int column,
int maxRow,
int maxColumn,
int offsetColumn)
where TPixel : struct, IPixel<TPixel>
{
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<TPixel> 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;
}
}
}

34
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.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[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;
}
/// <summary>
/// Reverses the result of premultiplying a vector via <see cref="Premultiply(Vector4)"/>.
/// Reverses the result of premultiplying a vector via <see cref="Premultiply(ref Vector4)"/>.
/// </summary>
/// <param name="source">The <see cref="Vector4"/> to premultiply</param>
/// <returns>The <see cref="Vector4"/></returns>
[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;
}
/// <summary>
/// Bulk variant of <see cref="Premultiply(Vector4)"/>
/// Bulk variant of <see cref="Premultiply(ref Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
[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);
}
}
/// <summary>
/// Bulk variant of <see cref="UnPremultiply(Vector4)"/>
/// Bulk variant of <see cref="UnPremultiply(ref Vector4)"/>
/// </summary>
/// <param name="vectors">The span of vectors</param>
[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);
}
}
}

10
src/ImageSharp/Primitives/DenseMatrix{T}.cs

@ -182,13 +182,13 @@ namespace SixLabors.ImageSharp.Primitives
}
/// <inheritdoc/>
public bool Equals(DenseMatrix<T> other) =>
this.Columns == other.Columns &&
this.Rows == other.Rows &&
this.Span.SequenceEqual(other.Span);
public override bool Equals(object obj) => obj is DenseMatrix<T> other && this.Equals(other);
/// <inheritdoc/>
public override bool Equals(object obj) => obj is DenseMatrix<T> other && this.Equals(other);
public bool Equals(DenseMatrix<T> other) =>
this.Columns == other.Columns
&& this.Rows == other.Rows
&& this.Span.SequenceEqual(other.Span);
/// <inheritdoc/>
public override int GetHashCode() => this.Data.GetHashCode();

90
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<float> matrixY = this.KernelY;
DenseMatrix<float> 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<TPixel> targetPixels =
configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
using (Buffer2D<TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(source.Width, source.Height))
{
source.CopyTo(targetPixels);
var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY);
int width = workingRectangle.Width;
ParallelHelper.IterateRows(
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
workingRectangle,
configuration,
rows =>
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel> 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<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
}
});

56
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -45,8 +45,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
using (Buffer2D<TPixel> firstPassPixels = configuration.MemoryAllocator.Allocate2D<TPixel>(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<TPixel> targetPixels,
Buffer2D<TPixel> sourcePixels,
Rectangle sourceRectangle,
DenseMatrix<float> kernel, // TODO: Can't use 'in' as pass by ref to lambda expression.
in DenseMatrix<float> kernel,
Configuration configuration)
{
int kernelHeight = kernel.Rows;
int kernelWidth = kernel.Columns;
int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1;
DenseMatrix<float> 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<Vector4>(
workingRectangle,
configuration,
rows =>
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel> 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<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
}
});
}

75
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 <see cref="ConvolutionProcessor{TPixel}"/> class.
/// </summary>
/// <param name="kernelXY">The 2d gradient operator.</param>
public ConvolutionProcessor(DenseMatrix<float> kernelXY)
{
this.KernelXY = kernelXY;
}
public ConvolutionProcessor(DenseMatrix<float> kernelXY) => this.KernelXY = kernelXY;
/// <summary>
/// Gets the 2d gradient operator.
@ -39,13 +32,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> 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<float> 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<Vector4>(
workingRectangle,
configuration,
rows =>
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
int length = vectorSpan.Length;
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
Span<TPixel> targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX);
PixelOperations<TPixel>.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<TPixel> 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<TPixel>.Instance.PackFromVector4(vectorSpan, targetRowSpan, length);
}
});

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

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

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

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

Loading…
Cancel
Save