Browse Source

Merge pull request #2371 from stefannikolei/stefannikolei/rewrite_colormatrix

Rewrite ColorMatrix
pull/2373/head
James Jackson-South 3 years ago
committed by GitHub
parent
commit
84b261ca5e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      src/ImageSharp/Common/Helpers/ColorNumerics.cs
  2. 42
      src/ImageSharp/Diagnostics/CodeAnalysis/UnscopedRefAttribute.cs
  3. 208
      src/ImageSharp/Primitives/ColorMatrix.Impl.cs
  4. 281
      src/ImageSharp/Primitives/ColorMatrix.cs
  5. 31
      tests/ImageSharp.Benchmarks/Bulk/ColorMatrixTransforms.cs
  6. 4
      tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs
  7. 2
      tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs
  8. 2
      tests/ImageSharp.Benchmarks/Bulk/FromVector4_Rgb24.cs
  9. 2
      tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs
  10. 29
      tests/ImageSharp.Benchmarks/Bulk/PremultiplyVector4.cs
  11. 2
      tests/ImageSharp.Benchmarks/Bulk/Rgb24Bytes.cs
  12. 2
      tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs
  13. 2
      tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs
  14. 2
      tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs
  15. 2
      tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs
  16. 2
      tests/ImageSharp.Benchmarks/Bulk/ToRgba32Bytes.cs
  17. 2
      tests/ImageSharp.Benchmarks/Bulk/ToVector4.cs
  18. 2
      tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs
  19. 2
      tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgb24.cs
  20. 2
      tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgba32.cs
  21. 29
      tests/ImageSharp.Benchmarks/Bulk/UnPremultiplyVector4.cs
  22. 34
      tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs
  23. 4
      tests/ImageSharp.Benchmarks/General/GetSetPixel.cs
  24. 10
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  25. 2
      tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs
  26. 35
      tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs

32
src/ImageSharp/Common/Helpers/ColorNumerics.cs

@ -3,7 +3,6 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp;
@ -17,7 +16,7 @@ internal static class ColorNumerics
/// Vector for converting pixel to gray value as specified by
/// ITU-R Recommendation BT.709.
/// </summary>
private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
private static readonly Vector4 Bt709 = new(.2126f, .7152f, .0722f, 0.0f);
/// <summary>
/// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
@ -137,6 +136,19 @@ internal static class ColorNumerics
public static int GetColorCountForBitDepth(int bitDepth)
=> 1 << bitDepth;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector4 Transform(Vector4 vector, in ColorMatrix.Impl matrix)
{
Vector4 result = matrix.X * vector.X;
result += matrix.Y * vector.Y;
result += matrix.Z * vector.Z;
result += matrix.W * vector.W;
result += matrix.V;
return result;
}
/// <summary>
/// Transforms a vector by the given color matrix.
/// </summary>
@ -144,17 +156,7 @@ internal static class ColorNumerics
/// <param name="matrix">The transformation color matrix.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Transform(ref Vector4 vector, ref ColorMatrix matrix)
{
float x = vector.X;
float y = vector.Y;
float z = vector.Z;
float w = vector.W;
vector.X = (x * matrix.M11) + (y * matrix.M21) + (z * matrix.M31) + (w * matrix.M41) + matrix.M51;
vector.Y = (x * matrix.M12) + (y * matrix.M22) + (z * matrix.M32) + (w * matrix.M42) + matrix.M52;
vector.Z = (x * matrix.M13) + (y * matrix.M23) + (z * matrix.M33) + (w * matrix.M43) + matrix.M53;
vector.W = (x * matrix.M14) + (y * matrix.M24) + (z * matrix.M34) + (w * matrix.M44) + matrix.M54;
}
=> vector = Transform(vector, matrix.AsImpl());
/// <summary>
/// Bulk variant of <see cref="Transform(ref Vector4, ref ColorMatrix)"/>.
@ -164,11 +166,9 @@ internal static class ColorNumerics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Transform(Span<Vector4> vectors, ref ColorMatrix matrix)
{
ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectors);
for (int i = 0; i < vectors.Length; i++)
{
ref Vector4 v = ref Unsafe.Add(ref baseRef, i);
ref Vector4 v = ref vectors[i];
Transform(ref v, ref matrix);
}
}

42
src/ImageSharp/Diagnostics/CodeAnalysis/UnscopedRefAttribute.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#if NET6_0
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>
/// Used to indicate a byref escapes and is not scoped.
/// </summary>
/// <remarks>
/// <para>
/// There are several cases where the C# compiler treats a <see langword="ref"/> as implicitly
/// <see langword="scoped"/> - where the compiler does not allow the <see langword="ref"/> to escape the method.
/// </para>
/// <para>
/// For example:
/// <list type="number">
/// <item><see langword="this"/> for <see langword="struct"/> instance methods.</item>
/// <item><see langword="ref"/> parameters that refer to <see langword="ref"/> <see langword="struct"/> types.</item>
/// <item><see langword="out"/> parameters.</item>
/// </list>
/// </para>
/// <para>
/// This attribute is used in those instances where the <see langword="ref"/> should be allowed to escape.
/// </para>
/// <para>
/// Applying this attribute, in any form, has impact on consumers of the applicable API. It is necessary for
/// API authors to understand the lifetime implications of applying this attribute and how it may impact their users.
/// </para>
/// </remarks>
[global::System.AttributeUsage(
global::System.AttributeTargets.Method |
global::System.AttributeTargets.Property |
global::System.AttributeTargets.Parameter,
AllowMultiple = false,
Inherited = false)]
internal sealed class UnscopedRefAttribute : global::System.Attribute
{
}
}
#endif

208
src/ImageSharp/Primitives/ColorMatrix.Impl.cs

@ -0,0 +1,208 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#pragma warning disable SA1117 // Parameters should be on same line or separate lines
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp;
/// <summary>
/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image.
/// </summary>
public partial struct ColorMatrix
{
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref Impl AsImpl() => ref Unsafe.As<ColorMatrix, Impl>(ref this);
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly ref readonly Impl AsROImpl() => ref Unsafe.As<ColorMatrix, Impl>(ref Unsafe.AsRef(in this));
internal struct Impl : IEquatable<Impl>
{
public Vector4 X;
public Vector4 Y;
public Vector4 Z;
public Vector4 W;
public Vector4 V;
public static Impl Identity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Impl result;
result.X = Vector4.UnitX;
result.Y = Vector4.UnitY;
result.Z = Vector4.UnitZ;
result.W = Vector4.UnitW;
result.V = Vector4.Zero;
return result;
}
}
public readonly bool IsIdentity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get =>
(this.X == Vector4.UnitX)
&& (this.Y == Vector4.UnitY)
&& (this.Z == Vector4.UnitZ)
&& (this.W == Vector4.UnitW)
&& (this.V == Vector4.Zero);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Impl operator +(in Impl left, in Impl right)
{
Impl result;
result.X = left.X + right.X;
result.Y = left.Y + right.Y;
result.Z = left.Z + right.Z;
result.W = left.W + right.W;
result.V = left.V + right.V;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Impl operator -(in Impl left, in Impl right)
{
Impl result;
result.X = left.X - right.X;
result.Y = left.Y - right.Y;
result.Z = left.Z - right.Z;
result.W = left.W - right.W;
result.V = left.V - right.V;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Impl operator -(in Impl value)
{
Impl result;
result.X = -value.X;
result.Y = -value.Y;
result.Z = -value.Z;
result.W = -value.W;
result.V = -value.V;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Impl operator *(in Impl left, in Impl right)
{
Impl result;
// result.X = Transform(left.X, in right);
result.X = right.X * left.X.X;
result.X += right.Y * left.X.Y;
result.X += right.Z * left.X.Z;
result.X += right.W * left.X.W;
// result.Y = Transform(left.Y, in right);
result.Y = right.X * left.Y.X;
result.Y += right.Y * left.Y.Y;
result.Y += right.Z * left.Y.Z;
result.Y += right.W * left.Y.W;
// result.Z = Transform(left.Z, in right);
result.Z = right.X * left.Z.X;
result.Z += right.Y * left.Z.Y;
result.Z += right.Z * left.Z.Z;
result.Z += right.W * left.Z.W;
// result.W = Transform(left.W, in right);
result.W = right.X * left.W.X;
result.W += right.Y * left.W.Y;
result.W += right.Z * left.W.Z;
result.W += right.W * left.W.W;
// result.V = Transform(left.V, in right);
result.V = right.X * left.V.X;
result.V += right.Y * left.V.Y;
result.V += right.Z * left.V.Z;
result.V += right.W * left.V.W;
result.V += right.V;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Impl operator *(in Impl left, float right)
{
Impl result;
result.X = left.X * right;
result.Y = left.Y * right;
result.Z = left.Z * right;
result.W = left.W * right;
result.V = left.V * right;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(in Impl left, in Impl right) =>
(left.X == right.X)
&& (left.Y == right.Y)
&& (left.Z == right.Z)
&& (left.W == right.W)
&& (left.V == right.V);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(in Impl left, in Impl right) =>
(left.X != right.X)
&& (left.Y != right.Y)
&& (left.Z != right.Z)
&& (left.W != right.W)
&& (left.V != right.V);
[UnscopedRef]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref ColorMatrix AsColorMatrix() => ref Unsafe.As<Impl, ColorMatrix>(ref this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Init(
float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44,
float m51, float m52, float m53, float m54)
{
this.X = new Vector4(m11, m12, m13, m14);
this.Y = new Vector4(m21, m22, m23, m24);
this.Z = new Vector4(m31, m32, m33, m34);
this.W = new Vector4(m41, m42, m43, m44);
this.V = new Vector4(m51, m52, m53, m54);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly bool Equals([NotNullWhen(true)] object? obj)
=> (obj is ColorMatrix other) && this.Equals(in other.AsImpl());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Equals(in Impl other) =>
this.X.Equals(other.X)
&& this.Y.Equals(other.Y)
&& this.Z.Equals(other.Z)
&& this.W.Equals(other.W)
&& this.V.Equals(other.V);
bool IEquatable<Impl>.Equals(Impl other) => this.Equals(in other);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y, this.Z, this.W, this.V);
}
}

281
src/ImageSharp/Primitives/ColorMatrix.cs

@ -2,7 +2,10 @@
// Licensed under the Six Labors Split License.
#pragma warning disable SA1117 // Parameters should be on same line or separate lines
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp;
@ -11,7 +14,7 @@ namespace SixLabors.ImageSharp;
/// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ColorMatrix : IEquatable<ColorMatrix>
public partial struct ColorMatrix : IEquatable<ColorMatrix>
{
/// <summary>
/// Value at row 1, column 1 of the matrix.
@ -137,62 +140,33 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="m53">The value at row 5, column 3 of the matrix.</param>
/// <param name="m54">The value at row 5, column 4 of the matrix.</param>
public ColorMatrix(float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44,
float m51, float m52, float m53, float m54)
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44,
float m51, float m52, float m53, float m54)
{
this.M11 = m11;
this.M12 = m12;
this.M13 = m13;
this.M14 = m14;
this.M21 = m21;
this.M22 = m22;
this.M23 = m23;
this.M24 = m24;
this.M31 = m31;
this.M32 = m32;
this.M33 = m33;
this.M34 = m34;
this.M41 = m41;
this.M42 = m42;
this.M43 = m43;
this.M44 = m44;
this.M51 = m51;
this.M52 = m52;
this.M53 = m53;
this.M54 = m54;
Unsafe.SkipInit(out this);
this.AsImpl().Init(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44, m51, m52, m53,
m54);
}
/// <summary>
/// Gets the multiplicative identity matrix.
/// </summary>
public static ColorMatrix Identity { get; } =
new ColorMatrix(1F, 0F, 0F, 0F,
0F, 1F, 0F, 0F,
0F, 0F, 1F, 0F,
0F, 0F, 0F, 1F,
0F, 0F, 0F, 0F);
public static ColorMatrix Identity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Impl.Identity.AsColorMatrix();
}
/// <summary>
/// Gets a value indicating whether the matrix is the identity matrix.
/// </summary>
public bool IsIdentity
{
get
{
// Check diagonal element first for early out.
return this.M11 == 1F && this.M22 == 1F && this.M33 == 1F && this.M44 == 1F
&& this.M12 == 0F && this.M13 == 0F && this.M14 == 0F
&& this.M21 == 0F && this.M23 == 0F && this.M24 == 0F
&& this.M31 == 0F && this.M32 == 0F && this.M34 == 0F
&& this.M41 == 0F && this.M42 == 0F && this.M43 == 0F
&& this.M51 == 0F && this.M52 == 0F && this.M53 == 0F && this.M54 == 0F;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.AsROImpl().IsIdentity;
}
/// <summary>
@ -202,32 +176,7 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value2">The second source matrix.</param>
/// <returns>The resulting matrix.</returns>
public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2)
{
var m = default(ColorMatrix);
m.M11 = value1.M11 + value2.M11;
m.M12 = value1.M12 + value2.M12;
m.M13 = value1.M13 + value2.M13;
m.M14 = value1.M14 + value2.M14;
m.M21 = value1.M21 + value2.M21;
m.M22 = value1.M22 + value2.M22;
m.M23 = value1.M23 + value2.M23;
m.M24 = value1.M24 + value2.M24;
m.M31 = value1.M31 + value2.M31;
m.M32 = value1.M32 + value2.M32;
m.M33 = value1.M33 + value2.M33;
m.M34 = value1.M34 + value2.M34;
m.M41 = value1.M41 + value2.M41;
m.M42 = value1.M42 + value2.M42;
m.M43 = value1.M43 + value2.M43;
m.M44 = value1.M44 + value2.M44;
m.M51 = value1.M51 + value2.M51;
m.M52 = value1.M52 + value2.M52;
m.M53 = value1.M53 + value2.M53;
m.M54 = value1.M54 + value2.M54;
return m;
}
=> (value1.AsImpl() + value2.AsImpl()).AsColorMatrix();
/// <summary>
/// Subtracts the second matrix from the first.
@ -236,32 +185,7 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value2">The second source matrix.</param>
/// <returns>The result of the subtraction.</returns>
public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2)
{
var m = default(ColorMatrix);
m.M11 = value1.M11 - value2.M11;
m.M12 = value1.M12 - value2.M12;
m.M13 = value1.M13 - value2.M13;
m.M14 = value1.M14 - value2.M14;
m.M21 = value1.M21 - value2.M21;
m.M22 = value1.M22 - value2.M22;
m.M23 = value1.M23 - value2.M23;
m.M24 = value1.M24 - value2.M24;
m.M31 = value1.M31 - value2.M31;
m.M32 = value1.M32 - value2.M32;
m.M33 = value1.M33 - value2.M33;
m.M34 = value1.M34 - value2.M34;
m.M41 = value1.M41 - value2.M41;
m.M42 = value1.M42 - value2.M42;
m.M43 = value1.M43 - value2.M43;
m.M44 = value1.M44 - value2.M44;
m.M51 = value1.M51 - value2.M51;
m.M52 = value1.M52 - value2.M52;
m.M53 = value1.M53 - value2.M53;
m.M54 = value1.M54 - value2.M54;
return m;
}
=> (value1.AsImpl() - value2.AsImpl()).AsColorMatrix();
/// <summary>
/// Returns a new matrix with the negated elements of the given matrix.
@ -269,32 +193,7 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value">The source matrix.</param>
/// <returns>The negated matrix.</returns>
public static ColorMatrix operator -(ColorMatrix value)
{
var m = default(ColorMatrix);
m.M11 = -value.M11;
m.M12 = -value.M12;
m.M13 = -value.M13;
m.M14 = -value.M14;
m.M21 = -value.M21;
m.M22 = -value.M22;
m.M23 = -value.M23;
m.M24 = -value.M24;
m.M31 = -value.M31;
m.M32 = -value.M32;
m.M33 = -value.M33;
m.M34 = -value.M34;
m.M41 = -value.M41;
m.M42 = -value.M42;
m.M43 = -value.M43;
m.M44 = -value.M44;
m.M51 = -value.M51;
m.M52 = -value.M52;
m.M53 = -value.M53;
m.M54 = -value.M54;
return m;
}
=> (-value.AsImpl()).AsColorMatrix();
/// <summary>
/// Multiplies a matrix by another matrix.
@ -303,41 +202,7 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value2">The second source matrix.</param>
/// <returns>The result of the multiplication.</returns>
public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2)
{
var m = default(ColorMatrix);
// First row
m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41);
m.M12 = (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32) + (value1.M14 * value2.M42);
m.M13 = (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33) + (value1.M14 * value2.M43);
m.M14 = (value1.M11 * value2.M14) + (value1.M12 * value2.M24) + (value1.M13 * value2.M34) + (value1.M14 * value2.M44);
// Second row
m.M21 = (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31) + (value1.M24 * value2.M41);
m.M22 = (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32) + (value1.M24 * value2.M42);
m.M23 = (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33) + (value1.M24 * value2.M43);
m.M24 = (value1.M21 * value2.M14) + (value1.M22 * value2.M24) + (value1.M23 * value2.M34) + (value1.M24 * value2.M44);
// Third row
m.M31 = (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31) + (value1.M34 * value2.M41);
m.M32 = (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32) + (value1.M34 * value2.M42);
m.M33 = (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33) + (value1.M34 * value2.M43);
m.M34 = (value1.M31 * value2.M14) + (value1.M32 * value2.M24) + (value1.M33 * value2.M34) + (value1.M34 * value2.M44);
// Fourth row
m.M41 = (value1.M41 * value2.M11) + (value1.M42 * value2.M21) + (value1.M43 * value2.M31) + (value1.M44 * value2.M41);
m.M42 = (value1.M41 * value2.M12) + (value1.M42 * value2.M22) + (value1.M43 * value2.M32) + (value1.M44 * value2.M42);
m.M43 = (value1.M41 * value2.M13) + (value1.M42 * value2.M23) + (value1.M43 * value2.M33) + (value1.M44 * value2.M43);
m.M44 = (value1.M41 * value2.M14) + (value1.M42 * value2.M24) + (value1.M43 * value2.M34) + (value1.M44 * value2.M44);
// Fifth row
m.M51 = (value1.M51 * value2.M11) + (value1.M52 * value2.M21) + (value1.M53 * value2.M31) + (value1.M54 * value2.M41) + value2.M51;
m.M52 = (value1.M51 * value2.M12) + (value1.M52 * value2.M22) + (value1.M53 * value2.M32) + (value1.M54 * value2.M52) + value2.M52;
m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53;
m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54;
return m;
}
=> (value1.AsImpl() * value2.AsImpl()).AsColorMatrix();
/// <summary>
/// Multiplies a matrix by a scalar value.
@ -346,32 +211,7 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value2">The scaling factor.</param>
/// <returns>The scaled matrix.</returns>
public static ColorMatrix operator *(ColorMatrix value1, float value2)
{
var m = default(ColorMatrix);
m.M11 = value1.M11 * value2;
m.M12 = value1.M12 * value2;
m.M13 = value1.M13 * value2;
m.M14 = value1.M14 * value2;
m.M21 = value1.M21 * value2;
m.M22 = value1.M22 * value2;
m.M23 = value1.M23 * value2;
m.M24 = value1.M24 * value2;
m.M31 = value1.M31 * value2;
m.M32 = value1.M32 * value2;
m.M33 = value1.M33 * value2;
m.M34 = value1.M34 * value2;
m.M41 = value1.M41 * value2;
m.M42 = value1.M42 * value2;
m.M43 = value1.M43 * value2;
m.M44 = value1.M44 * value2;
m.M51 = value1.M51 * value2;
m.M52 = value1.M52 * value2;
m.M53 = value1.M53 * value2;
m.M54 = value1.M54 * value2;
return m;
}
=> (value1.AsImpl() * value2).AsColorMatrix();
/// <summary>
/// Returns a boolean indicating whether the given two matrices are equal.
@ -379,7 +219,8 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value1">The first matrix to compare.</param>
/// <param name="value2">The second matrix to compare.</param>
/// <returns>True if the given matrices are equal; False otherwise.</returns>
public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2);
public static bool operator ==(ColorMatrix value1, ColorMatrix value2)
=> value1.AsImpl() == value2.AsImpl();
/// <summary>
/// Returns a boolean indicating whether the given two matrices are not equal.
@ -387,71 +228,35 @@ public struct ColorMatrix : IEquatable<ColorMatrix>
/// <param name="value1">The first matrix to compare.</param>
/// <param name="value2">The second matrix to compare.</param>
/// <returns>True if the given matrices are equal; False otherwise.</returns>
public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2);
public static bool operator !=(ColorMatrix value1, ColorMatrix value2)
=> value1.AsImpl() != value2.AsImpl();
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is ColorMatrix matrix && this.Equals(matrix);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly bool Equals([NotNullWhen(true)] object? obj)
=> this.AsROImpl().Equals(obj);
/// <inheritdoc/>
public bool Equals(ColorMatrix other) =>
this.M11 == other.M11
&& this.M12 == other.M12
&& this.M13 == other.M13
&& this.M14 == other.M14
&& this.M21 == other.M21
&& this.M22 == other.M22
&& this.M23 == other.M23
&& this.M24 == other.M24
&& this.M31 == other.M31
&& this.M32 == other.M32
&& this.M33 == other.M33
&& this.M34 == other.M34
&& this.M41 == other.M41
&& this.M42 == other.M42
&& this.M43 == other.M43
&& this.M44 == other.M44
&& this.M51 == other.M51
&& this.M52 == other.M52
&& this.M53 == other.M53
&& this.M54 == other.M54;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Equals(ColorMatrix other)
=> this.AsROImpl().Equals(in other.AsImpl());
/// <inheritdoc/>
public override int GetHashCode()
{
HashCode hash = default;
hash.Add(this.M11);
hash.Add(this.M12);
hash.Add(this.M13);
hash.Add(this.M14);
hash.Add(this.M21);
hash.Add(this.M22);
hash.Add(this.M23);
hash.Add(this.M24);
hash.Add(this.M31);
hash.Add(this.M32);
hash.Add(this.M33);
hash.Add(this.M34);
hash.Add(this.M41);
hash.Add(this.M42);
hash.Add(this.M43);
hash.Add(this.M44);
hash.Add(this.M51);
hash.Add(this.M52);
hash.Add(this.M53);
hash.Add(this.M54);
return hash.ToHashCode();
}
=> this.AsROImpl().GetHashCode();
/// <inheritdoc/>
public override string ToString()
{
CultureInfo ci = CultureInfo.CurrentCulture;
return string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}",
this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci),
this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci),
this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci),
this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci),
this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci));
return string.Format(
ci,
"{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}",
this.M11.ToString(ci), this.M12.ToString(ci), this.M13.ToString(ci), this.M14.ToString(ci),
this.M21.ToString(ci), this.M22.ToString(ci), this.M23.ToString(ci), this.M24.ToString(ci),
this.M31.ToString(ci), this.M32.ToString(ci), this.M33.ToString(ci), this.M34.ToString(ci),
this.M41.ToString(ci), this.M42.ToString(ci), this.M43.ToString(ci), this.M44.ToString(ci),
this.M51.ToString(ci), this.M52.ToString(ci), this.M53.ToString(ci), this.M54.ToString(ci));
}
}

31
tests/ImageSharp.Benchmarks/Bulk/ColorMatrixTransforms.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public class ColorMatrixTransforms
{
private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors();
[Benchmark(Baseline = true)]
public void Transform()
{
ColorMatrix matrix = KnownFilterMatrices.CreateHueFilter(45F);
for (int i = 0; i < Vectors.Length; i++)
{
ref Vector4 input = ref Vectors[i];
ColorNumerics.Transform(ref input, ref matrix);
}
}
[Benchmark]
public void Transform_Span()
{
ColorMatrix matrix = KnownFilterMatrices.CreateHueFilter(45F);
ColorNumerics.Transform(Vectors.AsSpan(), ref matrix);
}
}

4
tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs → tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public abstract class FromRgba32Bytes<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
@ -64,7 +64,7 @@ public abstract class FromRgba32Bytes<TPixel>
[Benchmark]
public void OptimizedBulk()
{
PixelOperations<TPixel>.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count);
PixelOperations<TPixel>.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count);
}
}

2
tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs → tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs

@ -12,7 +12,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.ShortCore31))]
public abstract class FromVector4<TPixel>

2
tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs → tests/ImageSharp.Benchmarks/Bulk/FromVector4_Rgb24.cs

@ -4,7 +4,7 @@
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.ShortMultiFramework))]
public class FromVector4_Rgb24 : FromVector4<Rgb24>

2
tests/ImageSharp.Benchmarks/Color/Bulk/Pad3Shuffle4Channel.cs → tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs

@ -3,7 +3,7 @@
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Pad3Shuffle4Channel

29
tests/ImageSharp.Benchmarks/Color/Bulk/PremultiplyVector4.cs → tests/ImageSharp.Benchmarks/Bulk/PremultiplyVector4.cs

@ -6,11 +6,11 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public class PremultiplyVector4
{
private static readonly Vector4[] Vectors = CreateVectors();
private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors();
[Benchmark(Baseline = true)]
public void PremultiplyBaseline()
@ -46,29 +46,4 @@ public class PremultiplyVector4
source *= w;
source.W = w;
}
private static Vector4[] CreateVectors()
{
Random rnd = new(42);
return GenerateRandomVectorArray(rnd, 2048, 0, 1);
}
private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal)
{
Vector4[] values = new Vector4[length];
for (int i = 0; i < length; i++)
{
ref Vector4 v = ref values[i];
v.X = GetRandomFloat(rnd, minVal, maxVal);
v.Y = GetRandomFloat(rnd, minVal, maxVal);
v.Z = GetRandomFloat(rnd, minVal, maxVal);
v.W = GetRandomFloat(rnd, minVal, maxVal);
}
return values;
}
private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
=> ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
}

2
tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs → tests/ImageSharp.Benchmarks/Bulk/Rgb24Bytes.cs

@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public abstract class Rgb24Bytes<TPixel>
where TPixel : unmanaged, IPixel<TPixel>

2
tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle3Channel.cs → tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs

@ -3,7 +3,7 @@
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Shuffle3Channel

2
tests/ImageSharp.Benchmarks/Color/Bulk/Shuffle4Slice3Channel.cs → tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs

@ -3,7 +3,7 @@
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Shuffle4Slice3Channel

2
tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleByte4Channel.cs → tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs

@ -3,7 +3,7 @@
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class ShuffleByte4Channel

2
tests/ImageSharp.Benchmarks/Color/Bulk/ShuffleFloat4Channel.cs → tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs

@ -4,7 +4,7 @@
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class ShuffleFloat4Channel

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs → tests/ImageSharp.Benchmarks/Bulk/ToRgba32Bytes.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public abstract class ToRgba32Bytes<TPixel>
where TPixel : unmanaged, IPixel<TPixel>

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs → tests/ImageSharp.Benchmarks/Bulk/ToVector4.cs

@ -9,7 +9,7 @@ using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public abstract class ToVector4<TPixel>
where TPixel : unmanaged, IPixel<TPixel>

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs → tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs

@ -6,7 +6,7 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Bgra32 : ToVector4<Bgra32>

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs → tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgb24.cs

@ -6,7 +6,7 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Rgb24 : ToVector4<Rgb24>

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs → tests/ImageSharp.Benchmarks/Bulk/ToVector4_Rgba32.cs

@ -9,7 +9,7 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
[Config(typeof(Config.ShortCore31))]
public class ToVector4_Rgba32 : ToVector4<Rgba32>

29
tests/ImageSharp.Benchmarks/Color/Bulk/UnPremultiplyVector4.cs → tests/ImageSharp.Benchmarks/Bulk/UnPremultiplyVector4.cs

@ -6,11 +6,11 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
public class UnPremultiplyVector4
{
private static readonly Vector4[] Vectors = CreateVectors();
private static readonly Vector4[] Vectors = Vector4Factory.CreateVectors();
[Benchmark(Baseline = true)]
public void UnPremultiplyBaseline()
@ -47,29 +47,4 @@ public class UnPremultiplyVector4
source /= w;
source.W = w;
}
private static Vector4[] CreateVectors()
{
Random rnd = new(42);
return GenerateRandomVectorArray(rnd, 2048, 0, 1);
}
private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal)
{
Vector4[] values = new Vector4[length];
for (int i = 0; i < length; i++)
{
ref Vector4 v = ref values[i];
v.X = GetRandomFloat(rnd, minVal, maxVal);
v.Y = GetRandomFloat(rnd, minVal, maxVal);
v.Z = GetRandomFloat(rnd, minVal, maxVal);
v.W = GetRandomFloat(rnd, minVal, maxVal);
}
return values;
}
private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
=> ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
}

34
tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Numerics;
namespace SixLabors.ImageSharp.Benchmarks.Bulk;
internal static class Vector4Factory
{
public static Vector4[] CreateVectors(int length = 2048, int min = 0, int max = 1)
{
Random rnd = new(42);
return GenerateRandomVectorArray(rnd, length, min, max);
}
private static Vector4[] GenerateRandomVectorArray(Random rnd, int length, float minVal, float maxVal)
{
Vector4[] values = new Vector4[length];
for (int i = 0; i < length; i++)
{
ref Vector4 v = ref values[i];
v.X = GetRandomFloat(rnd, minVal, maxVal);
v.Y = GetRandomFloat(rnd, minVal, maxVal);
v.Z = GetRandomFloat(rnd, minVal, maxVal);
v.W = GetRandomFloat(rnd, minVal, maxVal);
}
return values;
}
private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
=> (float)rnd.NextDouble() * (maxVal - minVal) + minVal;
}

4
tests/ImageSharp.Benchmarks/General/GetSetPixel.cs

@ -12,7 +12,7 @@ public class GetSetPixel
[Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")]
public System.Drawing.Color GetSetSystemDrawing()
{
using var source = new Bitmap(400, 400);
using Bitmap source = new(400, 400);
source.SetPixel(200, 200, System.Drawing.Color.White);
return source.GetPixel(200, 200);
}
@ -20,7 +20,7 @@ public class GetSetPixel
[Benchmark(Description = "ImageSharp GetSet pixel")]
public Rgba32 GetSetImageSharp()
{
using var image = new Image<Rgba32>(400, 400);
using Image<Rgba32> image = new(400, 400);
image[200, 200] = Color.White;
return image[200, 200];
}

10
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -11,7 +11,13 @@
<IsTestProject>false</IsTestProject>
<Configurations>Debug;Release</Configurations>
<!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): -->
<!--<SignAssembly>false</SignAssembly>-->
<!--<SignAssembly>false</SignAssembly>-->
</PropertyGroup>
<PropertyGroup>
<!--BenchmarkDotNet cannot run static benchmarks-->
<!--Mark members as static-->
<NoWarn>CA1822</NoWarn>
</PropertyGroup>
<Choose>
@ -53,7 +59,7 @@
<Compile Remove="Codecs\Jpeg\BlockOperations\**" />
<Compile Remove="Codecs\Jpeg\YCbCrColorConversion.cs" />
<Compile Remove="Codecs\Jpeg\DecodeJpegParseStreamOnly.cs" />
<Compile Remove="Color\Bulk\**" />
<Compile Remove="Bulk\**" />
<Compile Remove="Color\RgbToYCbCr.cs" />
<Compile Remove="Color\YCbCrToRgb.cs" />
<Compile Remove="General\Vectorization\**" />

2
tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs

@ -13,7 +13,7 @@ public class BokehBlur
[Benchmark]
public void Blur()
{
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
using Image<Rgba32> image = new(Configuration.Default, 400, 400, Color.White);
image.Mutate(c => c.BokehBlur());
}
}

35
tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Globalization;
using System.Numerics;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Tests.Primitives;
@ -115,27 +116,21 @@ public class ColorMatrixTests
public void ColorMatrixHashCode()
{
ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F);
HashCode hash = default;
hash.Add(m.M11);
hash.Add(m.M12);
hash.Add(m.M13);
hash.Add(m.M14);
hash.Add(m.M21);
hash.Add(m.M22);
hash.Add(m.M23);
hash.Add(m.M24);
hash.Add(m.M31);
hash.Add(m.M32);
hash.Add(m.M33);
hash.Add(m.M34);
hash.Add(m.M41);
hash.Add(m.M42);
hash.Add(m.M43);
hash.Add(m.M44);
hash.Add(m.M51);
hash.Add(m.M52);
hash.Add(m.M53);
hash.Add(m.M54);
Vector4 x = new(m.M11, m.M12, m.M13, m.M14);
Vector4 y = new(m.M21, m.M22, m.M23, m.M24);
Vector4 z = new(m.M31, m.M32, m.M33, m.M34);
Vector4 w = new(m.M41, m.M42, m.M43, m.M44);
Vector4 v = new(m.M51, m.M52, m.M53, m.M54);
hash.Add(x);
hash.Add(y);
hash.Add(z);
hash.Add(w);
hash.Add(v);
Assert.Equal(hash.ToHashCode(), m.GetHashCode());
}

Loading…
Cancel
Save