diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Extensions/SimdUtils.ExtendedIntrinsics.cs index ec52b90eff..97f364a109 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.ExtendedIntrinsics.cs @@ -24,6 +24,9 @@ namespace SixLabors.ImageSharp false; #endif + /// + /// A variant of , which is faster on new .NET runtime. + /// // ReSharper disable once MemberHidesStaticFromOuterClass internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) { @@ -37,7 +40,7 @@ namespace SixLabors.ImageSharp ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - var scale = new Vector(1f / 255f); + const float Scale = 1f / 255f; for (int i = 0; i < n; i++) { @@ -47,10 +50,10 @@ namespace SixLabors.ImageSharp Vector.Widen(s0, out Vector w0, out Vector w1); Vector.Widen(s1, out Vector w2, out Vector w3); - Vector f0 = Vector.ConvertToSingle(w0) * scale; - Vector f1 = Vector.ConvertToSingle(w1) * scale; - Vector f2 = Vector.ConvertToSingle(w2) * scale; - Vector f3 = Vector.ConvertToSingle(w3) * scale; + Vector f0 = Vector.ConvertToSingle(w0) * Scale; + Vector f1 = Vector.ConvertToSingle(w1) * Scale; + Vector f2 = Vector.ConvertToSingle(w2) * Scale; + Vector f3 = Vector.ConvertToSingle(w3) * Scale; ref Vector d = ref Unsafe.Add(ref destBase, i * 4); d = f0; @@ -59,6 +62,59 @@ namespace SixLabors.ImageSharp Unsafe.Add(ref d, 3) = f3; } } + + /// + /// A variant of , which is faster on new .NET runtime. + /// + // ReSharper disable once MemberHidesStaticFromOuterClass + internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + { + Guard.IsTrue( + dest.Length % Vector.Count == 0, + nameof(source), + "dest.Length should be divisable by Vector.Count!"); + + int n = dest.Length / Vector.Count; + + ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + for (int i = 0; i < n; i++) + { + ref Vector s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector f0 = s; + f0 = Clamp(f0); + + Vector f1 = Unsafe.Add(ref s, 1); + f1 = Clamp(f1); + + Vector f2 = Unsafe.Add(ref s, 2); + f2 = Clamp(f2); + + Vector f3 = Unsafe.Add(ref s, 3); + f3 = Clamp(f3); + + Vector w0 = Vector.ConvertToUInt32(f0 * 255f); + Vector w1 = Vector.ConvertToUInt32(f1 * 255f); + Vector w2 = Vector.ConvertToUInt32(f2 * 255f); + Vector w3 = Vector.ConvertToUInt32(f3 * 255f); + + Vector u0 = Vector.Narrow(w0, w1); + Vector u1 = Vector.Narrow(w2, w3); + + Vector b = Vector.Narrow(u0, u1); + + Unsafe.Add(ref destBase, i) = b; + } + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector Clamp(Vector x) + { + return Vector.Min(Vector.Max(x, Vector.Zero), Vector.One); + } } } } diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 0488dd5e15..4b23ca30f1 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -226,6 +226,24 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); } + [Theory] + [InlineData(1, 0)] + [InlineData(2, 32)] + [InlineData(3, 128)] + public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count) + { + float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444); + float[] normalized = orig.Select(f => f / 255f).ToArray(); + + byte[] dest = new byte[count]; + + SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(normalized, dest); + + byte[] expected = orig.Select(f => (byte)Clamp255(f)).ToArray(); + + Assert.Equal(expected, dest); + } + [Theory] [InlineData(0)] [InlineData(7)]