diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs
new file mode 100644
index 000000000..0596d8148
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykArm64.cs
@@ -0,0 +1,96 @@
+// 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 System.Runtime.Intrinsics.X86;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ internal sealed class CmykArm64 : JpegColorConverterArm
+ {
+ public CmykArm64(int precision)
+ : base(JpegColorSpace.Cmyk, precision)
+ {
+ }
+
+ ///
+ public override void ConvertToRgbInplace(in ComponentValues values)
+ {
+ ref Vector128 c0Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 c1Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 c2Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector128 c3Base =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ // Used for the color conversion
+ var scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
+
+ nint n = (nint)(uint)values.Component0.Length / Vector128.Count;
+ for (nint i = 0; i < n; i++)
+ {
+ ref Vector128 c = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 m = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 y = ref Unsafe.Add(ref c2Base, i);
+ Vector128 k = Unsafe.Add(ref c3Base, i);
+
+ k = AdvSimd.Multiply(k, scale);
+ c = AdvSimd.Multiply(c, k);
+ m = AdvSimd.Multiply(m, k);
+ y = AdvSimd.Multiply(y, k);
+ }
+ }
+
+ ///
+ public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane)
+ => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);
+
+ public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane)
+ {
+ ref Vector128 destC =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 destM =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 destY =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+ ref Vector128 destK =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ ref Vector128 srcR =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane));
+ ref Vector128 srcG =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane));
+ ref Vector128 srcB =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane));
+
+ var scale = Vector128.Create(maxValue);
+
+ nint n = (nint)(uint)values.Component0.Length / Vector128.Count;
+ for (nint i = 0; i < n; i++)
+ {
+ Vector128 ctmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcR, i));
+ Vector128 mtmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcG, i));
+ Vector128 ytmp = AdvSimd.Subtract(scale, Unsafe.Add(ref srcB, i));
+ Vector128 ktmp = AdvSimd.Min(ctmp, AdvSimd.Min(mtmp, ytmp));
+
+ Vector128 kMask = AdvSimd.CompareEqual(ktmp, scale);
+
+ ctmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(ctmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+ mtmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(mtmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+ ytmp = AdvSimd.And(AdvSimd.Arm64.Divide(AdvSimd.Subtract(ytmp, ktmp), AdvSimd.Subtract(scale, ktmp)), kMask);
+
+ Unsafe.Add(ref destC, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(ctmp, scale));
+ Unsafe.Add(ref destM, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(mtmp, scale));
+ Unsafe.Add(ref destY, i) = AdvSimd.Subtract(scale, AdvSimd.Multiply(ytmp, scale));
+ Unsafe.Add(ref destK, i) = AdvSimd.Subtract(scale, ktmp);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs
new file mode 100644
index 000000000..d6d4d6ef9
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterArm64.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ ///
+ /// abstract base for implementations
+ /// based on instructions.
+ ///
+ ///
+ /// Converters of this family would expect input buffers lengths to be
+ /// divisible by 8 without a remainder.
+ /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks.
+ /// DO NOT pass test data of invalid size to these converters as they
+ /// potentially won't do a bound check and return a false positive result.
+ ///
+ internal abstract class JpegColorConverterArm64 : JpegColorConverterBase
+ {
+ protected JpegColorConverterArm64(JpegColorSpace colorSpace, int precision)
+ : base(colorSpace, precision)
+ {
+ }
+
+ public static bool IsSupported => AdvSimd.Arm64.IsSupported;
+
+ public sealed override bool IsAvailable => IsSupported;
+
+ public sealed override int ElementsPerBatch => Vector128.Count;
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
index 66f0e9f5a..c6ad62310 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
@@ -176,6 +176,11 @@ internal abstract partial class JpegColorConverterBase
return new CmykAvx(precision);
}
+ if (JpegColorConverterArm64.IsSupported)
+ {
+ return new CmykArm64(precision);
+ }
+
if (JpegColorConverterVector.IsSupported)
{
return new CmykVector(precision);
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
index 6ad20ce67..51cd02bc7 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs
@@ -37,4 +37,12 @@ public class CmykColorConversion : ColorConversionBenchmark
new JpegColorConverterBase.CmykAvx(8).ConvertToRgbInplace(values);
}
+
+ [Benchmark]
+ public void SimdVectorArm64()
+ {
+ var values = new JpegColorConverterBase.ComponentValues(this.Input, 0);
+
+ new JpegColorConverterBase.CmykArm64(8).ConvertToRgbInplace(values);
+ }
}