diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
index ce6f335a8..8ba1b0912 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 @@ internal static partial class SimdUtils
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 000000000..a8d5f49c2
--- /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 5171c4986..67d40811d 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
@@ -200,6 +200,11 @@ internal abstract partial class JpegColorConverterBase
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 47aac3464..8bf26d721 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs
@@ -29,4 +29,12 @@ public class GrayscaleColorConversion : ColorConversionBenchmark
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 2eb988e75..72f89394d 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -470,6 +470,23 @@ public class JpegColorConverterTests
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) =>