diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs index ce6f335a8f..8ba1b09125 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.PixelFormats; @@ -554,6 +555,34 @@ public static Vector256 MultiplyAdd( return Avx.Add(Avx.Multiply(vm0, vm1), va); } + /// + /// Performs a multiplication and an addition of the . + /// TODO: Fix. The arguments are in a different order to the FMA intrinsic. + /// + /// ret = (vm0 * vm1) + va + /// The vector to add to the intermediate result. + /// The first vector to multiply. + /// The second vector to multiply. + /// The . + [MethodImpl(InliningOptions.AlwaysInline)] + public static Vector128 MultiplyAdd( + Vector128 va, + Vector128 vm0, + Vector128 vm1) + { + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(vm1, vm0, va); + } + + if (AdvSimd.IsSupported) + { + return AdvSimd.Add(AdvSimd.Multiply(vm0, vm1), va); + } + + return Sse.Add(Sse.Multiply(vm0, vm1), va); + } + /// /// Performs a multiplication and a subtraction of the . /// TODO: Fix. The arguments are in a different order to the FMA intrinsic. diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleArm.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleArm.cs new file mode 100644 index 0000000000..a8d5f49c24 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleArm.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using static SixLabors.ImageSharp.SimdUtils; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components; + +internal abstract partial class JpegColorConverterBase +{ + internal sealed class GrayscaleArm : JpegColorConverterArm + { + public GrayscaleArm(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + /// + public override void ConvertToRgbInplace(in ComponentValues values) + { + ref Vector128 c0Base = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + // Used for the color conversion + var scale = Vector128.Create(1 / this.MaximumValue); + + nuint n = (uint)values.Component0.Length / (uint)Vector128.Count; + for (nuint i = 0; i < n; i++) + { + ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); + c0 = AdvSimd.Multiply(c0, scale); + } + } + + /// + public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) + { + ref Vector128 destLuminance = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + ref Vector128 srcRed = + ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); + ref Vector128 srcGreen = + ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); + ref Vector128 srcBlue = + ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); + + // Used for the color conversion + var f0299 = Vector128.Create(0.299f); + var f0587 = Vector128.Create(0.587f); + var f0114 = Vector128.Create(0.114f); + + nuint n = (uint)values.Component0.Length / (uint)Vector128.Count; + for (nuint i = 0; i < n; i++) + { + ref Vector128 r = ref Unsafe.Add(ref srcRed, i); + ref Vector128 g = ref Unsafe.Add(ref srcGreen, i); + ref Vector128 b = ref Unsafe.Add(ref srcBlue, i); + + // luminocity = (0.299 * r) + (0.587 * g) + (0.114 * b) + Unsafe.Add(ref destLuminance, i) = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs index 58b0b9b1b2..44b4ed4ec3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs @@ -200,6 +200,11 @@ private static JpegColorConverterBase GetGrayScaleConverter(int precision) return new GrayscaleAvx(precision); } + if (JpegColorConverterArm.IsSupported) + { + return new GrayscaleArm(precision); + } + if (JpegColorConverterVector.IsSupported) { return new GrayScaleVector(precision); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 47aac3464e..8bf26d721d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -29,4 +29,12 @@ public void SimdVectorAvx() new JpegColorConverterBase.GrayscaleAvx(8).ConvertToRgbInplace(values); } + + [Benchmark] + public void SimdVectorArm() + { + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); + + new JpegColorConverterBase.GrayscaleArm(8).ConvertToRgbInplace(values); + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 44675aaea2..c71c70c336 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -307,6 +307,23 @@ public void FromRgbToGrayscaleAvx2(int seed) => new JpegColorConverterBase.GrayscaleScalar(8), precĂ­sion: 3); + [Theory] + [MemberData(nameof(Seeds))] + public void FromGrayscaleArm(int seed) => + this.TestConversionToRgb(new JpegColorConverterBase.GrayscaleArm(8), + 1, + seed, + new JpegColorConverterBase.GrayscaleScalar(8)); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbToGrayscaleArm(int seed) => + this.TestConversionFromRgb(new JpegColorConverterBase.GrayscaleArm(8), + 1, + seed, + new JpegColorConverterBase.GrayscaleScalar(8), + precĂ­sion: 3); + [Theory] [MemberData(nameof(Seeds))] public void FromRgbAvx2(int seed) => @@ -480,7 +497,7 @@ private static void ValidateConversionFromRgb( JpegColorConverterBase baseLineConverter, int precision = 4) { - // arrange + // arrange JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed); JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed); Random rnd = new(seed);