diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utils.cs
index 75bb00b6a..5c122217d 100644
--- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs
+++ b/src/ImageSharp/Common/Helpers/Vector4Utils.cs
@@ -5,6 +5,7 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp
{
@@ -70,5 +71,41 @@ namespace SixLabors.ImageSharp
UnPremultiply(ref v);
}
}
+
+ ///
+ /// Transforms a vector by the given matrix.
+ ///
+ /// The source vector.
+ /// The transformation matrix.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ 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;
+ }
+
+ ///
+ /// Bulk variant of
+ ///
+ /// The span of vectors
+ /// The transformation matrix.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void Transform(Span 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);
+ Transform(ref v, ref matrix);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs
new file mode 100644
index 000000000..af2e9465a
--- /dev/null
+++ b/src/ImageSharp/Primitives/ColorMatrix.cs
@@ -0,0 +1,459 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+#pragma warning disable SA1117 // Parameters should be on same line or separate lines
+using System;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Primitives
+{
+ ///
+ /// A structure encapsulating a 5x4 matrix used for transforming the color and alpha components of an image.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ColorMatrix : IEquatable
+ {
+ ///
+ /// Value at row 1, column 1 of the matrix.
+ ///
+ public float M11;
+
+ ///
+ /// Value at row 1, column 2 of the matrix.
+ ///
+ public float M12;
+
+ ///
+ /// Value at row 1, column 3 of the matrix.
+ ///
+ public float M13;
+
+ ///
+ /// Value at row 1, column 4 of the matrix.
+ ///
+ public float M14;
+
+ ///
+ /// Value at row 2, column 1 of the matrix.
+ ///
+ public float M21;
+
+ ///
+ /// Value at row 2, column 2 of the matrix.
+ ///
+ public float M22;
+
+ ///
+ /// Value at row 2, column 3 of the matrix.
+ ///
+ public float M23;
+
+ ///
+ /// Value at row 2, column 4 of the matrix.
+ ///
+ public float M24;
+
+ ///
+ /// Value at row 3, column 1 of the matrix.
+ ///
+ public float M31;
+
+ ///
+ /// Value at row 3, column 2 of the matrix.
+ ///
+ public float M32;
+
+ ///
+ /// Value at row 3, column 3 of the matrix.
+ ///
+ public float M33;
+
+ ///
+ /// Value at row 3, column 4 of the matrix.
+ ///
+ public float M34;
+
+ ///
+ /// Value at row 4, column 1 of the matrix.
+ ///
+ public float M41;
+
+ ///
+ /// Value at row 4, column 2 of the matrix.
+ ///
+ public float M42;
+
+ ///
+ /// Value at row 4, column 3 of the matrix.
+ ///
+ public float M43;
+
+ ///
+ /// Value at row 4, column 4 of the matrix.
+ ///
+ public float M44;
+
+ ///
+ /// Value at row 5, column 1 of the matrix.
+ ///
+ public float M51;
+
+ ///
+ /// Value at row 5, column 2 of the matrix.
+ ///
+ public float M52;
+
+ ///
+ /// Value at row 5, column 3 of the matrix.
+ ///
+ public float M53;
+
+ ///
+ /// Value at row 5, column 4 of the matrix.
+ ///
+ public float M54;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The value at row 1, column 1 of the matrix.
+ /// The value at row 1, column 2 of the matrix.
+ /// The value at row 1, column 3 of the matrix.
+ /// The value at row 1, column 4 of the matrix.
+ /// The value at row 2, column 1 of the matrix.
+ /// The value at row 2, column 2 of the matrix.
+ /// The value at row 2, column 3 of the matrix.
+ /// The value at row 2, column 4 of the matrix.
+ /// The value at row 3, column 1 of the matrix.
+ /// The value at row 3, column 2 of the matrix.
+ /// The value at row 3, column 3 of the matrix.
+ /// The value at row 3, column 4 of the matrix.
+ /// The value at row 4, column 1 of the matrix.
+ /// The value at row 4, column 2 of the matrix.
+ /// The value at row 4, column 3 of the matrix.
+ /// The value at row 4, column 4 of the matrix.
+ /// The value at row 5, column 1 of the matrix.
+ /// The value at row 5, column 2 of the matrix.
+ /// The value at row 5, column 3 of the matrix.
+ /// The value at row 5, column 4 of the matrix.
+ 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)
+ {
+ 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;
+ }
+
+ ///
+ /// Gets the multiplicative identity matrix.
+ ///
+ 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);
+
+ ///
+ /// Gets a value indicating whether the matrix is the identity matrix.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Adds two matrices together.
+ ///
+ /// The first source matrix.
+ /// The second source matrix.
+ /// The resulting matrix.
+ public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2)
+ {
+ ColorMatrix m;
+
+ 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;
+ }
+
+ ///
+ /// Subtracts the second matrix from the first.
+ ///
+ /// The first source matrix.
+ /// The second source matrix.
+ /// The result of the subtraction.
+ public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2)
+ {
+ ColorMatrix m;
+
+ 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;
+ }
+
+ ///
+ /// Returns a new matrix with the negated elements of the given matrix.
+ ///
+ /// The source matrix.
+ /// The negated matrix.
+ public static unsafe ColorMatrix operator -(ColorMatrix value)
+ {
+ ColorMatrix m;
+
+ 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;
+ }
+
+ ///
+ /// Multiplies a matrix by another matrix.
+ ///
+ /// The first source matrix.
+ /// The second source matrix.
+ /// The result of the multiplication.
+ public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2)
+ {
+ ColorMatrix m;
+
+ // 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;
+ }
+
+ ///
+ /// Multiplies a matrix by a scalar value.
+ ///
+ /// The source matrix.
+ /// The scaling factor.
+ /// The scaled matrix.
+ public static ColorMatrix operator *(ColorMatrix value1, float value2)
+ {
+ ColorMatrix m;
+
+ 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;
+ }
+
+ ///
+ /// Returns a boolean indicating whether the given two matrices are equal.
+ ///
+ /// The first matrix to compare.
+ /// The second matrix to compare.
+ /// True if the given matrices are equal; False otherwise.
+ public static bool operator ==(ColorMatrix value1, ColorMatrix value2) => value1.Equals(value2);
+
+ ///
+ /// Returns a boolean indicating whether the given two matrices are not equal.
+ ///
+ /// The first matrix to compare.
+ /// The second matrix to compare.
+ /// True if the given matrices are equal; False otherwise.
+ public static bool operator !=(ColorMatrix value1, ColorMatrix value2) => !value1.Equals(value2);
+
+ ///
+ public override bool Equals(object obj) => obj is ColorMatrix matrix && this.Equals(matrix);
+
+ ///
+ 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;
+
+ ///
+ 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();
+ }
+
+ ///
+ 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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/FilterExtensions.cs b/src/ImageSharp/Processing/FilterExtensions.cs
index bfae4ae65..70ac23286 100644
--- a/src/ImageSharp/Processing/FilterExtensions.cs
+++ b/src/ImageSharp/Processing/FilterExtensions.cs
@@ -1,8 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.Primitives;
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The filter color matrix
/// The .
- public static IImageProcessingContext Filter(this IImageProcessingContext source, Matrix4x4 matrix)
+ public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix)
where TPixel : struct, IPixel
=> source.ApplyProcessor(new FilterProcessor(matrix));
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The structure that specifies the portion of the image object to alter.
///
/// The .
- public static IImageProcessingContext Filter(this IImageProcessingContext source, Matrix4x4 matrix, Rectangle rectangle)
+ public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle)
where TPixel : struct, IPixel
=> source.ApplyProcessor(new FilterProcessor(matrix), rectangle);
}
diff --git a/src/ImageSharp/Processing/KnownFilterMatrices.cs b/src/ImageSharp/Processing/KnownFilterMatrices.cs
index cf0d19ff8..9c725d027 100644
--- a/src/ImageSharp/Processing/KnownFilterMatrices.cs
+++ b/src/ImageSharp/Processing/KnownFilterMatrices.cs
@@ -2,19 +2,28 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Numerics;
-
+using SixLabors.ImageSharp.Primitives;
+
+// Many of these matrices are tranlated from Chromium project where
+// SkScalar[] is memory-mapped to a row-major matrix.
+// The following translates to our column-major form:
+//
+// | 0| 1| 2| 3| 4| |0|5|10|15| |M11|M12|M13|M14|
+// | 5| 6| 7| 8| 9| |1|6|11|16| |M21|M22|M23|M24|
+// |10|11|12|13|14| = |2|7|12|17| = |M31|M32|M33|M34|
+// |15|16|17|18|19| |3|8|13|18| |M41|M42|M43|M44|
+// |4|9|14|19| |M51|M52|M53|M54|
namespace SixLabors.ImageSharp.Processing
{
///
- /// A collection of known values for composing filters
+ /// A collection of known values for composing filters
///
public static class KnownFilterMatrices
{
///
/// Gets a filter recreating Achromatomaly (Color desensitivity) color blindness
///
- public static Matrix4x4 AchromatomalyFilter { get; } = new Matrix4x4
+ public static ColorMatrix AchromatomalyFilter { get; } = new ColorMatrix
{
M11 = .618F,
M12 = .163F,
@@ -31,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// Gets a filter recreating Achromatopsia (Monochrome) color blindness.
///
- public static Matrix4x4 AchromatopsiaFilter { get; } = new Matrix4x4
+ public static ColorMatrix AchromatopsiaFilter { get; } = new ColorMatrix
{
M11 = .299F,
M12 = .299F,
@@ -42,97 +51,97 @@ namespace SixLabors.ImageSharp.Processing
M31 = .114F,
M32 = .114F,
M33 = .114F,
- M44 = 1
+ M44 = 1F
};
///
/// Gets a filter recreating Deuteranomaly (Green-Weak) color blindness.
///
- public static Matrix4x4 DeuteranomalyFilter { get; } = new Matrix4x4
+ public static ColorMatrix DeuteranomalyFilter { get; } = new ColorMatrix
{
- M11 = 0.8F,
- M12 = 0.258F,
- M21 = 0.2F,
- M22 = 0.742F,
- M23 = 0.142F,
- M33 = 0.858F,
- M44 = 1
+ M11 = .8F,
+ M12 = .258F,
+ M21 = .2F,
+ M22 = .742F,
+ M23 = .142F,
+ M33 = .858F,
+ M44 = 1F
};
///
/// Gets a filter recreating Deuteranopia (Green-Blind) color blindness.
///
- public static Matrix4x4 DeuteranopiaFilter { get; } = new Matrix4x4
+ public static ColorMatrix DeuteranopiaFilter { get; } = new ColorMatrix
{
- M11 = 0.625F,
- M12 = 0.7F,
- M21 = 0.375F,
- M22 = 0.3F,
- M23 = 0.3F,
- M33 = 0.7F,
- M44 = 1
+ M11 = .625F,
+ M12 = .7F,
+ M21 = .375F,
+ M22 = .3F,
+ M23 = .3F,
+ M33 = .7F,
+ M44 = 1F
};
///
/// Gets a filter recreating Protanomaly (Red-Weak) color blindness.
///
- public static Matrix4x4 ProtanomalyFilter { get; } = new Matrix4x4
+ public static ColorMatrix ProtanomalyFilter { get; } = new ColorMatrix
{
- M11 = 0.817F,
- M12 = 0.333F,
- M21 = 0.183F,
- M22 = 0.667F,
- M23 = 0.125F,
- M33 = 0.875F,
- M44 = 1
+ M11 = .817F,
+ M12 = .333F,
+ M21 = .183F,
+ M22 = .667F,
+ M23 = .125F,
+ M33 = .875F,
+ M44 = 1F
};
///
/// Gets a filter recreating Protanopia (Red-Blind) color blindness.
///
- public static Matrix4x4 ProtanopiaFilter { get; } = new Matrix4x4
+ public static ColorMatrix ProtanopiaFilter { get; } = new ColorMatrix
{
- M11 = 0.567F,
- M12 = 0.558F,
- M21 = 0.433F,
- M22 = 0.442F,
- M23 = 0.242F,
- M33 = 0.758F,
- M44 = 1
+ M11 = .567F,
+ M12 = .558F,
+ M21 = .433F,
+ M22 = .442F,
+ M23 = .242F,
+ M33 = .758F,
+ M44 = 1F
};
///
/// Gets a filter recreating Tritanomaly (Blue-Weak) color blindness.
///
- public static Matrix4x4 TritanomalyFilter { get; } = new Matrix4x4
+ public static ColorMatrix TritanomalyFilter { get; } = new ColorMatrix
{
- M11 = 0.967F,
- M21 = 0.33F,
- M22 = 0.733F,
- M23 = 0.183F,
- M32 = 0.267F,
- M33 = 0.817F,
- M44 = 1
+ M11 = .967F,
+ M21 = .33F,
+ M22 = .733F,
+ M23 = .183F,
+ M32 = .267F,
+ M33 = .817F,
+ M44 = 1F
};
///
/// Gets a filter recreating Tritanopia (Blue-Blind) color blindness.
///
- public static Matrix4x4 TritanopiaFilter { get; } = new Matrix4x4
+ public static ColorMatrix TritanopiaFilter { get; } = new ColorMatrix
{
- M11 = 0.95F,
- M21 = 0.05F,
- M22 = 0.433F,
- M23 = 0.475F,
- M32 = 0.567F,
- M33 = 0.525F,
- M44 = 1
+ M11 = .95F,
+ M21 = .05F,
+ M22 = .433F,
+ M23 = .475F,
+ M32 = .567F,
+ M33 = .525F,
+ M44 = 1F
};
///
/// Gets an approximated black and white filter
///
- public static Matrix4x4 BlackWhiteFilter { get; } = new Matrix4x4()
+ public static ColorMatrix BlackWhiteFilter { get; } = new ColorMatrix()
{
M11 = 1.5F,
M12 = 1.5F,
@@ -143,24 +152,24 @@ namespace SixLabors.ImageSharp.Processing
M31 = 1.5F,
M32 = 1.5F,
M33 = 1.5F,
- M41 = -1F,
- M42 = -1F,
- M43 = -1F,
- M44 = 1
+ M44 = 1F,
+ M51 = -1F,
+ M52 = -1F,
+ M53 = -1F,
};
///
/// Gets a filter recreating an old Kodachrome camera effect.
///
- public static Matrix4x4 KodachromeFilter { get; } = new Matrix4x4
+ public static ColorMatrix KodachromeFilter { get; } = new ColorMatrix
{
- M11 = 0.7297023F,
- M22 = 0.6109577F,
- M33 = 0.597218F,
- M41 = 0.105F,
- M42 = 0.145F,
- M43 = 0.155F,
- M44 = 1
+ M11 = .7297023F,
+ M22 = .6109577F,
+ M33 = .597218F,
+ M44 = 1F,
+ M51 = .105F,
+ M52 = .145F,
+ M53 = .155F,
}
* CreateSaturateFilter(1.2F) * CreateContrastFilter(1.35F);
@@ -168,15 +177,15 @@ namespace SixLabors.ImageSharp.Processing
///
/// Gets a filter recreating an old Lomograph camera effect.
///
- public static Matrix4x4 LomographFilter { get; } = new Matrix4x4
+ public static ColorMatrix LomographFilter { get; } = new ColorMatrix
{
M11 = 1.5F,
M22 = 1.45F,
M33 = 1.16F,
- M41 = -.1F,
- M42 = -.02F,
- M43 = -.07F,
- M44 = 1
+ M44 = 1F,
+ M51 = -.1F,
+ M52 = -.02F,
+ M53 = -.07F,
}
* CreateSaturateFilter(1.1F) * CreateContrastFilter(1.33F);
@@ -184,21 +193,21 @@ namespace SixLabors.ImageSharp.Processing
///
/// Gets a filter recreating an old Polaroid camera effect.
///
- public static Matrix4x4 PolaroidFilter { get; } = new Matrix4x4
+ public static ColorMatrix PolaroidFilter { get; } = new ColorMatrix
{
M11 = 1.538F,
- M12 = -0.062F,
- M13 = -0.262F,
- M21 = -0.022F,
+ M12 = -.062F,
+ M13 = -.262F,
+ M21 = -.022F,
M22 = 1.578F,
- M23 = -0.022F,
+ M23 = -.022F,
M31 = .216F,
M32 = -.16F,
M33 = 1.5831F,
- M41 = 0.02F,
- M42 = -0.05F,
- M43 = -0.05F,
- M44 = 1
+ M44 = 1F,
+ M51 = .02F,
+ M52 = -.05F,
+ M53 = -.05F
};
///
@@ -209,18 +218,18 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing brighter results.
///
/// The proportion of the conversion. Must be greater than or equal to 0.
- /// The
- public static Matrix4x4 CreateBrightnessFilter(float amount)
+ /// The
+ public static ColorMatrix CreateBrightnessFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
+ return new ColorMatrix
{
M11 = amount,
M22 = amount,
M33 = amount,
- M44 = 1
+ M44 = 1F
};
}
@@ -232,23 +241,23 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing results with more contrast.
///
/// The proportion of the conversion. Must be greater than or equal to 0.
- /// The
- public static Matrix4x4 CreateContrastFilter(float amount)
+ /// The
+ public static ColorMatrix CreateContrastFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
float contrast = (-.5F * amount) + .5F;
- return new Matrix4x4
+ return new ColorMatrix
{
M11 = amount,
M22 = amount,
M33 = amount,
- M41 = contrast,
- M42 = contrast,
- M43 = contrast,
- M44 = 1
+ M44 = 1F,
+ M51 = contrast,
+ M52 = contrast,
+ M53 = contrast
};
}
@@ -257,26 +266,27 @@ namespace SixLabors.ImageSharp.Processing
///
///
/// The proportion of the conversion. Must be between 0 and 1.
- /// The
- public static Matrix4x4 CreateGrayscaleBt601Filter(float amount)
+ /// The
+ public static ColorMatrix CreateGrayscaleBt601Filter(float amount)
{
- Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
+ Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount));
amount = 1F - amount;
- // https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
- {
- M11 = .299F + (.701F * amount),
- M12 = .299F - (.299F * amount),
- M13 = .299F - (.299F * amount),
- M21 = .587F - (.587F * amount),
- M22 = .587F + (.413F * amount),
- M23 = .587F - (.587F * amount),
- M31 = .114F - (.114F * amount),
- M32 = .114F - (.114F * amount),
- M33 = .114F + (.886F * amount),
- M44 = 1
- };
+ ColorMatrix m = default;
+ m.M11 = .299F + (.701F * amount);
+ m.M21 = .587F - (.587F * amount);
+ m.M31 = 1F - (m.M11 + m.M21);
+
+ m.M12 = .299F - (.299F * amount);
+ m.M22 = .587F + (.2848F * amount);
+ m.M32 = 1F - (m.M12 + m.M22);
+
+ m.M13 = .299F - (.299F * amount);
+ m.M23 = .587F - (.587F * amount);
+ m.M33 = 1F - (m.M13 + m.M23);
+ m.M44 = 1F;
+
+ return m;
}
///
@@ -284,34 +294,36 @@ namespace SixLabors.ImageSharp.Processing
///
///
/// The proportion of the conversion. Must be between 0 and 1.
- /// The
- public static Matrix4x4 CreateGrayscaleBt709Filter(float amount)
+ /// The
+ public static ColorMatrix CreateGrayscaleBt709Filter(float amount)
{
- Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
+ Guard.MustBeBetweenOrEqualTo(amount, 0, 1F, nameof(amount));
amount = 1F - amount;
// https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
- {
- M11 = .2126F + (.7874F * amount),
- M12 = .2126F - (.2126F * amount),
- M13 = .2126F - (.2126F * amount),
- M21 = .7152F - (.7152F * amount),
- M22 = .7152F + (.2848F * amount),
- M23 = .7152F - (.7152F * amount),
- M31 = .0722F - (.0722F * amount),
- M32 = .0722F - (.0722F * amount),
- M33 = .0722F + (.9278F * amount),
- M44 = 1
- };
+ ColorMatrix m = default;
+ m.M11 = .2126F + (.7874F * amount);
+ m.M21 = .7152F - (.7152F * amount);
+ m.M31 = 1F - (m.M11 + m.M21);
+
+ m.M12 = .2126F - (.2126F * amount);
+ m.M22 = .7152F + (.2848F * amount);
+ m.M32 = 1F - (m.M12 + m.M22);
+
+ m.M13 = .2126F - (.2126F * amount);
+ m.M23 = .7152F - (.7152F * amount);
+ m.M33 = 1F - (m.M13 + m.M23);
+ m.M44 = 1F;
+
+ return m;
}
///
/// Create a hue filter matrix using the given angle in degrees.
///
/// The angle of rotation in degrees.
- /// The
- public static Matrix4x4 CreateHueFilter(float degrees)
+ /// The
+ public static ColorMatrix CreateHueFilter(float degrees)
{
// Wrap the angle round at 360.
degrees %= 360;
@@ -329,18 +341,20 @@ namespace SixLabors.ImageSharp.Processing
// The matrix is set up to preserve the luminance of the image.
// See http://graficaobscura.com/matrix/index.html
// Number are taken from https://msdn.microsoft.com/en-us/library/jj192162(v=vs.85).aspx
- return new Matrix4x4
+ return new ColorMatrix
{
M11 = .213F + (cosRadian * .787F) - (sinRadian * .213F),
- M12 = .213F - (cosRadian * .213F) - (sinRadian * 0.143F),
- M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F),
M21 = .715F - (cosRadian * .715F) - (sinRadian * .715F),
- M22 = .715F + (cosRadian * .285F) + (sinRadian * 0.140F),
- M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F),
M31 = .072F - (cosRadian * .072F) + (sinRadian * .928F),
- M32 = .072F - (cosRadian * .072F) - (sinRadian * 0.283F),
+
+ M12 = .213F - (cosRadian * .213F) + (sinRadian * .143F),
+ M22 = .715F + (cosRadian * .285F) + (sinRadian * .140F),
+ M32 = .072F - (cosRadian * .072F) - (sinRadian * .283F),
+
+ M13 = .213F - (cosRadian * .213F) - (sinRadian * .787F),
+ M23 = .715F - (cosRadian * .715F) + (sinRadian * .715F),
M33 = .072F + (cosRadian * .928F) + (sinRadian * .072F),
- M44 = 1
+ M44 = 1F
};
}
@@ -348,23 +362,23 @@ namespace SixLabors.ImageSharp.Processing
/// Create an invert filter matrix using the given amount.
///
/// The proportion of the conversion. Must be between 0 and 1.
- /// The
- public static Matrix4x4 CreateInvertFilter(float amount)
+ /// The
+ public static ColorMatrix CreateInvertFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
float invert = 1F - (2F * amount);
- return new Matrix4x4
+ return new ColorMatrix
{
M11 = invert,
M22 = invert,
M33 = invert,
- M41 = amount,
- M42 = amount,
- M43 = amount,
- M44 = 1
+ M44 = 1F,
+ M51 = amount,
+ M52 = amount,
+ M53 = amount,
};
}
@@ -372,17 +386,17 @@ namespace SixLabors.ImageSharp.Processing
/// Create an opacity filter matrix using the given amount.
///
/// The proportion of the conversion. Must be between 0 and 1.
- /// The
- public static Matrix4x4 CreateOpacityFilter(float amount)
+ /// The
+ public static ColorMatrix CreateOpacityFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
+ return new ColorMatrix
{
- M11 = 1,
- M22 = 1,
- M33 = 1,
+ M11 = 1F,
+ M22 = 1F,
+ M33 = 1F,
M44 = amount
};
}
@@ -395,25 +409,27 @@ namespace SixLabors.ImageSharp.Processing
/// Other values are linear multipliers on the effect. Values of amount over 1 are allowed, providing super-saturated results
///
/// The proportion of the conversion. Must be greater than or equal to 0.
- /// The
- public static Matrix4x4 CreateSaturateFilter(float amount)
+ /// The
+ public static ColorMatrix CreateSaturateFilter(float amount)
{
Guard.MustBeGreaterThanOrEqualTo(amount, 0, nameof(amount));
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
- {
- M11 = .213F + (.787F * amount),
- M12 = .213F - (.213F * amount),
- M13 = .213F - (.213F * amount),
- M21 = .715F - (.715F * amount),
- M22 = .715F + (.285F * amount),
- M23 = .715F - (.715F * amount),
- M31 = 1F - ((.213F + (.787F * amount)) + (.715F - (.715F * amount))),
- M32 = 1F - ((.213F - (.213F * amount)) + (.715F + (.285F * amount))),
- M33 = 1F - ((.213F - (.213F * amount)) + (.715F - (.715F * amount))),
- M44 = 1
- };
+ ColorMatrix m = default;
+ m.M11 = .213F + (.787F * amount);
+ m.M21 = .715F - (.715F * amount);
+ m.M31 = 1F - (m.M11 + m.M21);
+
+ m.M12 = .213F - (.213F * amount);
+ m.M22 = .715F + (.285F * amount);
+ m.M32 = 1F - (m.M12 + m.M22);
+
+ m.M13 = .213F - (.213F * amount);
+ m.M23 = .715F - (.715F * amount);
+ m.M33 = 1F - (m.M13 + m.M23);
+ m.M44 = 1F;
+
+ return m;
}
///
@@ -421,25 +437,27 @@ namespace SixLabors.ImageSharp.Processing
/// The formula used matches the svg specification.
///
/// The proportion of the conversion. Must be between 0 and 1.
- /// The
- public static Matrix4x4 CreateSepiaFilter(float amount)
+ /// The
+ public static ColorMatrix CreateSepiaFilter(float amount)
{
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
amount = 1F - amount;
// See https://cs.chromium.org/chromium/src/cc/paint/render_surface_filters.cc
- return new Matrix4x4
+ return new ColorMatrix
{
M11 = .393F + (.607F * amount),
- M12 = .349F - (.349F * amount),
- M13 = .272F - (.272F * amount),
M21 = .769F - (.769F * amount),
- M22 = .686F + (.314F * amount),
- M23 = .534F - (.534F * amount),
M31 = .189F - (.189F * amount),
+
+ M12 = .349F - (.349F * amount),
+ M22 = .686F + (.314F * amount),
M32 = .168F - (.168F * amount),
+
+ M13 = .272F - (.272F * amount),
+ M23 = .534F - (.534F * amount),
M33 = .131F + (.869F * amount),
- M44 = 1
+ M44 = 1F
};
}
}
diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
index d3a44c066..d6a32d889 100644
--- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs
@@ -3,16 +3,16 @@
using System;
using System.Numerics;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Filters
{
///
- /// Provides methods that accept a matrix to apply free-form filters to images.
+ /// Provides methods that accept a matrix to apply free-form filters to images.
///
/// The pixel format.
internal class FilterProcessor : ImageProcessor
@@ -22,38 +22,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// Initializes a new instance of the class.
///
/// The matrix used to apply the image filter
- public FilterProcessor(Matrix4x4 matrix)
- {
- this.Matrix = matrix;
- }
+ public FilterProcessor(ColorMatrix matrix) => this.Matrix = matrix;
///
- /// Gets the used to apply the image filter.
+ /// Gets the used to apply the image filter.
///
- public Matrix4x4 Matrix { get; }
+ public ColorMatrix Matrix { get; }
///
protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds());
+ int startX = interest.X;
- Matrix4x4 matrix = this.Matrix;
+ ColorMatrix matrix = this.Matrix;
- ParallelHelper.IterateRows(
+ ParallelHelper.IterateRowsWithTempBuffer(
interest,
configuration,
- rows =>
+ (rows, vectorBuffer) =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
- Span row = source.GetPixelRowSpan(y);
-
- for (int x = interest.X; x < interest.Right; x++)
- {
- ref TPixel pixel = ref row[x];
- var vector = Vector4.Transform(pixel.ToVector4(), matrix);
- pixel.FromVector4(vector);
- }
+ Span vectorSpan = vectorBuffer.Span;
+ int length = vectorSpan.Length;
+ Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length);
+ PixelOperations.Instance.ToVector4(configuration, rowSpan, vectorSpan);
+
+ Vector4Utils.Transform(vectorSpan, ref matrix);
+
+ PixelOperations.Instance.FromVector4(configuration, vectorSpan, rowSpan);
}
});
}
diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
index 158a085d5..aed1edfbf 100644
--- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
+++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
@@ -57,8 +57,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image image = file.CreateImage())
{
- var encoder = new PngEncoder { Quantizer = new WuQuantizer(KnownDiffusers.JarvisJudiceNinke, 256), ColorType = PngColorType.Palette };
- image.Save($"{path}/{file.FileName}.png", encoder);
+ image.Save($"{path}/{file.FileName}");
}
}
}
diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs
index 9416be740..f2e98b131 100644
--- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs
@@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{
public class Vector4UtilsTests
{
+ private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f);
+
[Theory]
[InlineData(0)]
[InlineData(1)]
@@ -23,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Vector4Utils.Premultiply(source);
- Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ Assert.Equal(expected, source, this.ApproximateFloatComparer);
}
[Theory]
@@ -38,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Vector4Utils.UnPremultiply(source);
- Assert.Equal(expected, source, new ApproximateFloatComparer(1e-6f));
+ Assert.Equal(expected, source, this.ApproximateFloatComparer);
}
}
}
diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs
new file mode 100644
index 000000000..2fbe260ec
--- /dev/null
+++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Globalization;
+using SixLabors.ImageSharp.Primitives;
+using SixLabors.ImageSharp.Processing;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Primitives
+{
+ public class ColorMatrixTests
+ {
+ private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f);
+
+ [Fact]
+ public void ColorMatrixIdentityIsCorrect()
+ {
+ ColorMatrix val = default;
+ val.M11 = val.M22 = val.M33 = val.M44 = 1F;
+
+ Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer);
+ }
+
+ [Fact]
+ public void ColorMatrixCanDetectIdentity()
+ {
+ ColorMatrix m = ColorMatrix.Identity;
+ Assert.True(m.IsIdentity);
+
+ m.M12 = 1F;
+ Assert.False(m.IsIdentity);
+ }
+
+ [Fact]
+ public void ColorMatrixEquality()
+ {
+ ColorMatrix m = KnownFilterMatrices.CreateHueFilter(45F);
+ ColorMatrix m2 = KnownFilterMatrices.CreateHueFilter(45F);
+ object obj = m2;
+
+ Assert.True(m.Equals(obj));
+ Assert.True(m.Equals(m2));
+ Assert.True(m == m2);
+ Assert.False(m != m2);
+ }
+
+ [Fact]
+ public void ColorMatrixMultiply()
+ {
+ ColorMatrix value1 = this.CreateAllTwos();
+ ColorMatrix value2 = this.CreateAllThrees();
+
+ ColorMatrix m;
+
+ // 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;
+
+ Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer);
+ }
+
+ [Fact]
+ public void ColorMatrixMultiplyScalar()
+ {
+ ColorMatrix m = this.CreateAllTwos();
+ Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer);
+ }
+
+ [Fact]
+ public void ColorMatrixSubtract()
+ {
+ ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos();
+ Assert.Equal(this.CreateAllThrees(), m);
+ }
+
+ [Fact]
+ public void ColorMatrixNegate()
+ {
+ ColorMatrix m = this.CreateAllOnes() * -1F;
+ Assert.Equal(m, -this.CreateAllOnes());
+ }
+
+ [Fact]
+ public void ColorMatrixAdd()
+ {
+ ColorMatrix m = this.CreateAllOnes() + this.CreateAllTwos();
+ Assert.Equal(this.CreateAllThrees(), m);
+ }
+
+ [Fact]
+ public void ColorMatrixHashCode()
+ {
+#if NETCOREAPP2_1
+ 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);
+
+ Assert.Equal(hash.ToHashCode(), m.GetHashCode());
+#endif
+ }
+
+ [Fact]
+ public void ColorMatrixToString()
+ {
+ ColorMatrix m = KnownFilterMatrices.CreateBrightnessFilter(.5F);
+
+ CultureInfo ci = CultureInfo.CurrentCulture;
+
+ string expected = 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}}} }}",
+ m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci),
+ m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci),
+ m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci),
+ m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci),
+ m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci));
+
+ Assert.Equal(expected, m.ToString());
+ }
+
+ private ColorMatrix CreateAllOnes()
+ {
+ return new ColorMatrix
+ {
+ M11 = 1F,
+ M12 = 1F,
+ M13 = 1F,
+ M14 = 1F,
+ M21 = 1F,
+ M22 = 1F,
+ M23 = 1F,
+ M24 = 1F,
+ M31 = 1F,
+ M32 = 1F,
+ M33 = 1F,
+ M34 = 1F,
+ M41 = 1F,
+ M42 = 1F,
+ M43 = 1F,
+ M44 = 1F,
+ M51 = 1F,
+ M52 = 1F,
+ M53 = 1F,
+ M54 = 1F
+ };
+ }
+
+ private ColorMatrix CreateAllTwos()
+ {
+ return new ColorMatrix
+ {
+ M11 = 2F,
+ M12 = 2F,
+ M13 = 2F,
+ M14 = 2F,
+ M21 = 2F,
+ M22 = 2F,
+ M23 = 2F,
+ M24 = 2F,
+ M31 = 2F,
+ M32 = 2F,
+ M33 = 2F,
+ M34 = 2F,
+ M41 = 2F,
+ M42 = 2F,
+ M43 = 2F,
+ M44 = 2F,
+ M51 = 2F,
+ M52 = 2F,
+ M53 = 2F,
+ M54 = 2F
+ };
+ }
+
+ private ColorMatrix CreateAllThrees()
+ {
+ return new ColorMatrix
+ {
+ M11 = 3F,
+ M12 = 3F,
+ M13 = 3F,
+ M14 = 3F,
+ M21 = 3F,
+ M22 = 3F,
+ M23 = 3F,
+ M24 = 3F,
+ M31 = 3F,
+ M32 = 3F,
+ M33 = 3F,
+ M34 = 3F,
+ M41 = 3F,
+ M42 = 3F,
+ M43 = 3F,
+ M44 = 3F,
+ M51 = 3F,
+ M52 = 3F,
+ M53 = 3F,
+ M54 = 3F
+ };
+ }
+
+ private ColorMatrix CreateAllFours()
+ {
+ return new ColorMatrix
+ {
+ M11 = 4F,
+ M12 = 4F,
+ M13 = 4F,
+ M14 = 4F,
+ M21 = 4F,
+ M22 = 4F,
+ M23 = 4F,
+ M24 = 4F,
+ M31 = 4F,
+ M32 = 4F,
+ M33 = 4F,
+ M34 = 4F,
+ M41 = 4F,
+ M42 = 4F,
+ M43 = 4F,
+ M44 = 4F,
+ M51 = 4F,
+ M52 = 4F,
+ M53 = 4F,
+ M54 = 4F
+ };
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs
index ed790cbac..54a8dd4b7 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs
@@ -8,10 +8,13 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects
{
using SixLabors.ImageSharp.Processing;
+ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Filters")]
public class BrightnessTest
{
+ private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.007F);
+
public static readonly TheoryData BrightnessValues
= new TheoryData
{
@@ -22,9 +25,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects
[Theory]
[WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)]
public void ApplyBrightnessFilter(TestImageProvider provider, float value)
- where TPixel : struct, IPixel
- {
- provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value);
- }
+ where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer);
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs
index 3d48e16ec..8ac56655e 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs
@@ -2,17 +2,19 @@
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
{
using SixLabors.ImageSharp.Processing;
+ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
[GroupOutput("Filters")]
public class ColorBlindnessTest
{
+ private readonly ImageComparer imageComparer = ImageComparer.Tolerant(0.03F);
+
public static readonly TheoryData ColorBlindnessFilters
= new TheoryData
{
@@ -29,9 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
[Theory]
[WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)]
public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness)
- where TPixel : struct, IPixel
- {
- provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString());
- }
+ where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer);
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
index 479a3c33a..68daa80ea 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
@@ -1,16 +1,13 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
-
-using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
{
+ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
[GroupOutput("Filters")]
@@ -25,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
public void ApplyFilter(TestImageProvider provider)
where TPixel : struct, IPixel
{
- Matrix4x4 m = CreateCombinedTestFilterMatrix();
+ ColorMatrix m = CreateCombinedTestFilterMatrix();
provider.RunValidatingProcessorTest(x => x.Filter(m), comparer: ValidatorComparer);
}
@@ -35,18 +32,17 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
public void ApplyFilterInBox(TestImageProvider provider)
where TPixel : struct, IPixel
{
- Matrix4x4 m = CreateCombinedTestFilterMatrix();
+ ColorMatrix m = CreateCombinedTestFilterMatrix();
provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer);
}
- private static Matrix4x4 CreateCombinedTestFilterMatrix()
+ private static ColorMatrix CreateCombinedTestFilterMatrix()
{
- Matrix4x4 brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F);
- Matrix4x4 hue = KnownFilterMatrices.CreateHueFilter(180F);
- Matrix4x4 saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F);
- Matrix4x4 m = brightness * hue * saturation;
- return m;
+ ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F);
+ ColorMatrix hue = KnownFilterMatrices.CreateHueFilter(180F);
+ ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F);
+ return brightness * hue * saturation;
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
index 47ca6cccb..872a935ff 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Numerics;
+using SixLabors.ImageSharp.Primitives;
namespace SixLabors.ImageSharp.Tests
{
@@ -11,8 +12,9 @@ namespace SixLabors.ImageSharp.Tests
///
internal readonly struct ApproximateFloatComparer :
IEqualityComparer,
+ IEqualityComparer,
IEqualityComparer,
- IEqualityComparer
+ IEqualityComparer
{
private readonly float Epsilon;
@@ -20,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests
/// Initializes a new instance of the class.
///
/// The comparison error difference epsilon to use.
- public ApproximateFloatComparer(float epsilon = 1f) => this.Epsilon = epsilon;
+ public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon;
///
public bool Equals(float x, float y)
@@ -34,17 +36,29 @@ namespace SixLabors.ImageSharp.Tests
public int GetHashCode(float obj) => obj.GetHashCode();
///
- public bool Equals(Vector4 a, Vector4 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
+ public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
///
- public int GetHashCode(Vector4 obj) => obj.GetHashCode();
+ public int GetHashCode(Vector2 obj) => obj.GetHashCode();
+
+ ///
+ public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
///
- public bool Equals(Vector2 a, Vector2 b) => this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y);
+ public int GetHashCode(Vector4 obj) => obj.GetHashCode();
- public int GetHashCode(Vector2 obj)
+ ///
+ public bool Equals(ColorMatrix x, ColorMatrix y)
{
- throw new System.NotImplementedException();
+ return
+ this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
+ && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24)
+ && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34)
+ && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44)
+ && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54);
}
+
+ ///
+ public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();
}
}
\ No newline at end of file
diff --git a/tests/Images/External b/tests/Images/External
index 7ada45bc3..74995302e 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 7ada45bc3484f40e28a50817386ca93f293acd11
+Subproject commit 74995302e3c9a5913f1cf09d71b15f888d0ec022