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); + } }