diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 865c28f3d8..e97d8bd286 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// public static class SRgbCompanding { - private const int Length = Scale + 2; - private const int Scale = (1 << 14) - 1; + private const int Length = Scale + 2; // 256kb @ 16bit precision. + private const int Scale = (1 << 16) - 1; private static readonly Lazy LazyCompressTable = new Lazy(() => { @@ -129,10 +129,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Expand(vector.X); vector.Y = Expand(vector.Y); vector.Z = Expand(vector.Z); - vector.W = Expand(vector.W); } /// @@ -142,10 +142,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Compress(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Compress(vector.X); vector.Y = Compress(vector.Y); vector.Z = Compress(vector.Z); - vector.W = Compress(vector.W); } /// @@ -192,7 +192,9 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); - vectorsBase = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + // Alpha is already a linear representation of opacity so we do not want to convert it. + Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } } @@ -216,17 +218,15 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding float f0 = multiplied.X; float f1 = multiplied.Y; float f2 = multiplied.Z; - float f3 = multiplied.W; uint i0 = (uint)f0; uint i1 = (uint)f1; uint i2 = (uint)f2; - uint i3 = (uint)f3; + // Alpha is already a linear representation of opacity so we do not want to convert it. vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); - vectorsBase.W = Numerics.Lerp(tablePointer[i3], tablePointer[i3 + 1], f3 - (int)i3); vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 0ff8b30826..6105422372 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp internal static class Numerics { #if SUPPORTS_RUNTIME_INTRINSICS - private const int BlendAlphaControl = 0b_10_00_10_00; + public const int BlendAlphaControl = 0b_10_00_10_00; private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index acc2725cec..7af2662765 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 39786a2177..ebb9747770 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => { Span destPixels = d.GetSpan(); - this.Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); }); } @@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void SourceAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Compress(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v); Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -219,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); @@ -254,7 +249,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -297,7 +293,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -343,7 +340,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); + TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan())); } [Theory] @@ -356,11 +353,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => - { - Span destVectors = d.GetSpan(); - this.Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); - }); + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Scale)); } [Theory] @@ -369,13 +366,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - SRgbCompanding.Compress(ref v); } - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -396,13 +389,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -419,13 +408,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -446,8 +431,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); - SRgbCompanding.Compress(ref v); } void ExpectedAction(ref Vector4 v) @@ -1006,15 +989,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Theory] [MemberData(nameof(ArraySizesData))] public void PackFromRgbPlanes(int count) - { - SimdUtilsTests.TestPackFromRgbPlanes( + => SimdUtilsTests.TestPackFromRgbPlanes( count, - ( - r, - g, - b, - actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); - } + (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); public delegate void RefAction(ref T1 arg1); @@ -1071,7 +1048,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); result[i] = v; @@ -1088,7 +1065,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1106,7 +1083,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1129,10 +1106,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations return result; } - internal static Vector4 GetVector(Random rnd) - { - return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); - } + internal static Vector4 GetScaledVector(Random rnd) + => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); [StructLayout(LayoutKind.Sequential)] internal unsafe struct OctetBytes @@ -1177,14 +1152,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + var comparer = new ApproximateFloatComparer(0.0001F); - var comparer = new ApproximateFloatComparer(0.001f); for (int i = 0; i < count; i++) { - // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); + } + } + else if (typeof(IPixel).IsAssignableFrom(typeof(TDest))) + { + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); + var comparer = new ApproximateFloatComparer(0.0001F); - // ReSharper restore PossibleNullReferenceException + for (int i = 0; i < count; i++) + { + Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer); } } else diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index b2f390dcdf..2a0ed19b48 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.PixelFormats; namespace SixLabors.ImageSharp.Tests { @@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, + IEqualityComparer, IEqualityComparer, IEqualityComparer { @@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests } /// - public int GetHashCode(float obj) => obj.GetHashCode(); + public int GetHashCode(float obj) + => obj.GetHashCode(); /// - public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + public bool Equals(Vector2 x, Vector2 y) + => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); /// - public int GetHashCode(Vector2 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(IPixel x, IPixel y) + => this.Equals(x.ToScaledVector4(), y.ToScaledVector4()); + + public int GetHashCode(IPixel obj) + => obj.ToScaledVector4().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 int GetHashCode(Vector4 obj) => obj.GetHashCode(); + public int GetHashCode(Vector4 obj) + => obj.GetHashCode(); /// public bool Equals(ColorMatrix x, ColorMatrix y) - { - 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.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();