diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs
index 876f6e1fe6..5af5db3cda 100644
--- a/src/ImageSharp/Common/Helpers/Numerics.cs
+++ b/src/ImageSharp/Common/Helpers/Numerics.cs
@@ -949,4 +949,94 @@ internal static class Numerics
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOutOfRange(int value, int min, int max)
=> (uint)(value - min) > (uint)(max - min);
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint VectorCount(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector128Count(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector128.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector128Count(this ReadOnlySpan span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector128.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector256Count(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector256.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector256Count(this ReadOnlySpan span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector256.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint VectorCount(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector128Count(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector128.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into the given span.
+ ///
+ /// The type of the vector.
+ /// The given span.
+ /// Count of vectors that safely fit into the span.
+ public static nuint Vector256Count(this Span span)
+ where TVector : struct
+ => (uint)span.Length / (uint)Vector256.Count;
+
+ ///
+ /// Gets the count of vectors that safely fit into length.
+ ///
+ /// The type of the vector.
+ /// The given length.
+ /// Count of vectors that safely fit into the length.
+ public static nuint Vector256Count(int length)
+ where TVector : struct
+ => (uint)length / (uint)Vector256.Count;
}
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
index b9505ba3ef..ac122fc7d4 100644
--- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
+++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs
@@ -97,7 +97,7 @@ internal static partial class SimdUtils
{
VerifySpanInput(source, dest, Vector.Count);
- nuint n = (uint)dest.Length / (uint)Vector.Count;
+ nuint n = dest.VectorCount();
ref Vector sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
ref Vector destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
@@ -132,7 +132,7 @@ internal static partial class SimdUtils
{
VerifySpanInput(source, dest, Vector.Count);
- nuint n = (uint)dest.Length / (uint)Vector.Count;
+ nuint n = dest.VectorCount();
ref Vector sourceBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
index ce6f335a8f..5b5a3493de 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;
@@ -221,7 +222,7 @@ internal static partial class SimdUtils
ref Vector256 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
- nint n = (nint)(uint)(dest.Length / Vector256.Count);
+ nint n = (nint)dest.Vector256Count();
nint m = Numerics.Modulo4(n);
nint u = n - m;
@@ -391,7 +392,7 @@ internal static partial class SimdUtils
ref Vector128 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
- nuint n = (uint)source.Length / (uint)Vector128.Count;
+ nuint n = source.Vector128Count();
for (nuint i = 0; i < n; i += 3)
{
@@ -454,7 +455,7 @@ internal static partial class SimdUtils
ref Vector128 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
- nuint n = (uint)source.Length / (uint)Vector128.Count;
+ nuint n = source.Vector128Count();
for (nuint i = 0, j = 0; i < n; i += 3, j += 4)
{
@@ -498,7 +499,7 @@ internal static partial class SimdUtils
ref Vector128 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
- nuint n = (uint)source.Length / (uint)Vector128.Count;
+ nuint n = source.Vector128Count();
for (nuint i = 0, j = 0; i < n; i += 4, j += 3)
{
@@ -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.
@@ -650,7 +679,7 @@ internal static partial class SimdUtils
{
VerifySpanInput(source, dest, Vector256.Count);
- nuint n = (uint)dest.Length / (uint)Vector256.Count;
+ nuint n = dest.Vector256Count();
ref Vector256 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
@@ -683,7 +712,7 @@ internal static partial class SimdUtils
// Sse
VerifySpanInput(source, dest, Vector128.Count);
- nuint n = (uint)dest.Length / (uint)Vector128.Count;
+ nuint n = dest.Vector128Count();
ref Vector128 destBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(dest));
@@ -782,7 +811,7 @@ internal static partial class SimdUtils
{
VerifySpanInput(source, dest, Vector256.Count);
- nuint n = (uint)dest.Length / (uint)Vector256.Count;
+ nuint n = dest.Vector256Count();
ref Vector256 sourceBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
@@ -821,7 +850,7 @@ internal static partial class SimdUtils
// Sse
VerifySpanInput(source, dest, Vector128.Count);
- nuint n = (uint)dest.Length / (uint)Vector128.Count;
+ nuint n = dest.Vector128Count();
ref Vector128 sourceBase =
ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
@@ -864,7 +893,7 @@ internal static partial class SimdUtils
ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel));
ref byte dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination));
- nuint count = (uint)redChannel.Length / (uint)Vector256.Count;
+ nuint count = redChannel.Vector256Count();
ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32);
Vector256 control1 = Unsafe.As>(ref control1Bytes);
@@ -936,7 +965,7 @@ internal static partial class SimdUtils
ref Vector256 bBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(blueChannel));
ref Vector256 dBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination));
- nuint count = (uint)redChannel.Length / (uint)Vector256.Count;
+ nuint count = redChannel.Vector256Count();
ref byte control1Bytes = ref MemoryMarshal.GetReference(PermuteMaskEvenOdd8x32);
Vector256 control1 = Unsafe.As>(ref control1Bytes);
var a = Vector256.Create((byte)255);
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 0c1b273f77..863fed359c 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -453,6 +453,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle4(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span cmd = stackalloc byte[2];
int count = 0;
@@ -491,9 +492,9 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
int max = cmd[1];
int bytesToRead = (int)(((uint)max + 1) / 2);
- byte[] run = new byte[bytesToRead];
+ Span run = bytesToRead <= 128 ? scratchBuffer.Slice(0, bytesToRead) : new byte[bytesToRead];
- stream.Read(run, 0, run.Length);
+ stream.Read(run);
int idx = 0;
for (int i = 0; i < max; i++)
@@ -559,6 +560,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle8(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span cmd = stackalloc byte[2];
int count = 0;
@@ -596,13 +598,13 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
- byte[] run = new byte[length];
+ Span run = length <= 128 ? scratchBuffer.Slice(0, length) : new byte[length];
- stream.Read(run, 0, run.Length);
+ stream.Read(run);
- run.AsSpan().CopyTo(buffer[count..]);
+ run.CopyTo(buffer[count..]);
- count += run.Length;
+ count += length;
// Absolute mode data is aligned to two-byte word-boundary.
int padding = length & 1;
@@ -639,6 +641,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
/// Keeps track of rows, which have undefined pixels.
private void UncompressRle24(BufferedReadStream stream, int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
+ Span scratchBuffer = stackalloc byte[128];
Span cmd = stackalloc byte[2];
int uncompressedPixels = 0;
@@ -675,17 +678,18 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// If the second byte > 2, we are in 'absolute mode'.
// Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
+ int length3 = length * 3;
- byte[] run = new byte[length * 3];
+ Span run = length3 <= 128 ? scratchBuffer.Slice(0, length3) : new byte[length3];
- stream.Read(run, 0, run.Length);
+ stream.Read(run);
- run.AsSpan().CopyTo(buffer[(uncompressedPixels * 3)..]);
+ run.CopyTo(buffer[(uncompressedPixels * 3)..]);
uncompressedPixels += length;
// Absolute mode data is aligned to two-byte word-boundary.
- int padding = run.Length & 1;
+ int padding = length3 & 1;
stream.Skip(padding);
@@ -1286,18 +1290,18 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
// color masks for each color channel follow the info header.
if (this.infoHeader.Compression == BmpCompression.BitFields)
{
- byte[] bitfieldsBuffer = new byte[12];
- stream.Read(bitfieldsBuffer, 0, 12);
- Span data = bitfieldsBuffer.AsSpan();
+ Span bitfieldsBuffer = stackalloc byte[12];
+ stream.Read(bitfieldsBuffer);
+ Span data = bitfieldsBuffer;
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
}
else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS)
{
- byte[] bitfieldsBuffer = new byte[16];
- stream.Read(bitfieldsBuffer, 0, 16);
- Span data = bitfieldsBuffer.AsSpan();
+ Span bitfieldsBuffer = stackalloc byte[16];
+ stream.Read(bitfieldsBuffer);
+ Span data = bitfieldsBuffer;
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data[..4]);
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
@@ -1470,7 +1474,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
{
// Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
// Make sure, that we will not read pass the bitmap offset (starting position of image data).
- if ((stream.Position + colorMapSizeBytes) > this.fileHeader.Offset)
+ if (stream.Position > this.fileHeader.Offset - colorMapSizeBytes)
{
BmpThrowHelper.ThrowInvalidImageContentException(
$"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSizeBytes}' is invalid or the bitmap offset.");
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 6ff2723ddd..55ad2c4585 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -22,7 +22,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
///
/// The temp buffer used to reduce allocations.
///
- private readonly byte[] buffer = new byte[16];
+ private ScratchBuffer buffer; // mutable struct, don't make readonly
///
/// The global color table.
@@ -249,13 +249,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// The containing image data.
private void ReadGraphicalControlExtension(BufferedReadStream stream)
{
- int bytesRead = stream.Read(this.buffer, 0, 6);
+ int bytesRead = stream.Read(this.buffer.Span, 0, 6);
if (bytesRead != 6)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
}
- this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
+ this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer.Span);
}
///
@@ -264,13 +264,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// The containing image data.
private void ReadImageDescriptor(BufferedReadStream stream)
{
- int bytesRead = stream.Read(this.buffer, 0, 9);
+ int bytesRead = stream.Read(this.buffer.Span, 0, 9);
if (bytesRead != 9)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
}
- this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
+ this.imageDescriptor = GifImageDescriptor.Parse(this.buffer.Span);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
{
GifThrowHelper.ThrowInvalidImageContentException("Width or height should not be 0");
@@ -283,13 +283,13 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// The containing image data.
private void ReadLogicalScreenDescriptor(BufferedReadStream stream)
{
- int bytesRead = stream.Read(this.buffer, 0, 7);
+ int bytesRead = stream.Read(this.buffer.Span, 0, 7);
if (bytesRead != 7)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
}
- this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
+ this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer.Span);
}
///
@@ -306,8 +306,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
long position = stream.Position;
if (appLength == GifConstants.ApplicationBlockSize)
{
- stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize);
- bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes);
+ stream.Read(this.buffer.Span, 0, GifConstants.ApplicationBlockSize);
+ bool isXmp = this.buffer.Span.StartsWith(GifConstants.XmpApplicationIdentificationBytes);
if (isXmp && !this.skipMetadata)
{
GifXmpApplicationExtension extension = GifXmpApplicationExtension.Read(stream, this.memoryAllocator);
@@ -331,8 +331,8 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
// http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
{
- stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
- this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
+ stream.Read(this.buffer.Span, 0, GifConstants.NetscapeLoopingSubBlockSize);
+ this.gifMetadata!.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.Span.Slice(1)).RepeatCount;
stream.Skip(1); // Skip the terminator.
return;
}
@@ -762,4 +762,12 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
}
}
+
+ private unsafe struct ScratchBuffer
+ {
+ private const int Size = 16;
+ private fixed byte scratch[Size];
+
+ public Span Span => MemoryMarshal.CreateSpan(ref this.scratch[0], Size);
+ }
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index f736da78dd..c01cc78ef0 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -28,11 +28,6 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
///
private readonly Configuration configuration;
- ///
- /// A reusable buffer used to reduce allocations.
- ///
- private readonly byte[] buffer = new byte[20];
-
///
/// Whether to skip metadata during encode.
///
@@ -324,9 +319,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
- descriptor.WriteTo(this.buffer);
+ Span buffer = stackalloc byte[20];
+ descriptor.WriteTo(buffer);
- stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
+ stream.Write(buffer, 0, GifLogicalScreenDescriptor.Size);
}
///
@@ -365,12 +361,14 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
return;
}
+ Span buffer = stackalloc byte[2];
+
for (int i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
- this.buffer[0] = GifConstants.ExtensionIntroducer;
- this.buffer[1] = GifConstants.CommentLabel;
- stream.Write(this.buffer, 0, 2);
+ buffer[1] = GifConstants.CommentLabel;
+ buffer[0] = GifConstants.ExtensionIntroducer;
+ stream.Write(buffer);
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan commentSpan = comment.AsSpan();
@@ -437,22 +435,23 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
private void WriteExtension(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{
- IMemoryOwner? owner = null;
- Span extensionBuffer;
int extensionSize = extension.ContentLength;
if (extensionSize == 0)
{
return;
}
- else if (extensionSize > this.buffer.Length - 3)
+
+ IMemoryOwner? owner = null;
+ Span extensionBuffer = stackalloc byte[0]; // workaround compiler limitation
+ if (extensionSize > 128)
{
owner = this.memoryAllocator.Allocate(extensionSize + 3);
extensionBuffer = owner.GetSpan();
}
else
{
- extensionBuffer = this.buffer;
+ extensionBuffer = stackalloc byte[extensionSize + 3];
}
extensionBuffer[0] = GifConstants.ExtensionIntroducer;
@@ -489,9 +488,10 @@ internal sealed class GifEncoderCore : IImageEncoderInternals
height: (ushort)image.Height,
packed: packedValue);
- descriptor.WriteTo(this.buffer);
+ Span buffer = stackalloc byte[20];
+ descriptor.WriteTo(buffer);
- stream.Write(this.buffer, 0, GifImageDescriptor.Size);
+ stream.Write(buffer, 0, GifImageDescriptor.Size);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs
index 0283e83d0f..76d188f457 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykAvx.cs
@@ -32,7 +32,7 @@ internal abstract partial class JpegColorConverterBase
// Used for the color conversion
var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
ref Vector256 c = ref Unsafe.Add(ref c0Base, i);
@@ -71,7 +71,7 @@ internal abstract partial class JpegColorConverterBase
var scale = Vector256.Create(maxValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
Vector256 ctmp = Avx.Subtract(scale, Unsafe.Add(ref srcR, i));
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
index 4cc8c0665f..a59be009b7 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector.cs
@@ -30,7 +30,7 @@ internal abstract partial class JpegColorConverterBase
var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue));
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
ref Vector c = ref Unsafe.Add(ref cBase, i);
@@ -78,7 +78,7 @@ internal abstract partial class JpegColorConverterBase
// Used for the color conversion
var scale = new Vector(maxValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
Vector ctmp = scale - Unsafe.Add(ref srcR, i);
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..6bb8fd7aa3
--- /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 = values.Component0.Vector128Count();
+ 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 = values.Component0.Vector128Count();
+ 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/JpegColorConverter.GrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs
index 4efb6f8514..a9e1c5d130 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleAvx.cs
@@ -27,7 +27,7 @@ internal abstract partial class JpegColorConverterBase
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i);
@@ -53,7 +53,7 @@ internal abstract partial class JpegColorConverterBase
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
ref Vector256 r = ref Unsafe.Add(ref srcRed, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
index 4156a5968b..cac10636f5 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector.cs
@@ -24,7 +24,7 @@ internal abstract partial class JpegColorConverterBase
var scale = new Vector(1 / this.MaximumValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
ref Vector c0 = ref Unsafe.Add(ref cBase, i);
@@ -53,7 +53,7 @@ internal abstract partial class JpegColorConverterBase
var gMult = new Vector(0.587f);
var bMult = new Vector(0.114f);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
Vector r = Unsafe.Add(ref srcR, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbArm.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbArm.cs
index 8889223064..75eeb17dd3 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbArm.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbArm.cs
@@ -30,7 +30,7 @@ internal abstract partial class JpegColorConverterBase
// Used for the color conversion
var scale = Vector128.Create(1 / this.MaximumValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector128.Count;
+ nuint n = values.Component0.Vector128Count();
for (nuint i = 0; i < n; i++)
{
ref Vector128 r = ref Unsafe.Add(ref rBase, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs
index ae9b943aaf..b56728fa71 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbAvx.cs
@@ -29,7 +29,7 @@ internal abstract partial class JpegColorConverterBase
// Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
ref Vector256 r = ref Unsafe.Add(ref rBase, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
index 5e3cf9b2b3..bd3142fa13 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector.cs
@@ -28,7 +28,7 @@ internal abstract partial class JpegColorConverterBase
var scale = new Vector(1 / this.MaximumValue);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
ref Vector r = ref Unsafe.Add(ref rBase, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs
new file mode 100644
index 0000000000..4f7cf1ed65
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrArm.cs
@@ -0,0 +1,122 @@
+// 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;
+using static SixLabors.ImageSharp.SimdUtils;
+
+// ReSharper disable ImpureMethodCallOnReadonlyValueField
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ internal sealed class YCbCrArm : JpegColorConverterArm
+ {
+ public YCbCrArm(int precision)
+ : base(JpegColorSpace.YCbCr, 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));
+
+ // Used for the color conversion
+ var chromaOffset = Vector128.Create(-this.HalfValue);
+ var scale = Vector128.Create(1 / this.MaximumValue);
+ var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
+ var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
+ var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
+ var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
+
+ // Walking 8 elements at one step:
+ nuint n = (uint)values.Component0.Length / (uint)Vector128.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ // cb = cbVals[i] - 128F;
+ // cr = crVals[i] - 128F;
+ ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i);
+
+ Vector128 y = c0;
+ Vector128 cb = AdvSimd.Add(c1, chromaOffset);
+ Vector128 cr = AdvSimd.Add(c2, chromaOffset);
+
+ // r = y + (1.402F * cr);
+ // g = y - (0.344136F * cb) - (0.714136F * cr);
+ // b = y + (1.772F * cb);
+ Vector128 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult);
+ Vector128 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
+ Vector128 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
+
+ r = AdvSimd.Multiply(AdvSimd.RoundToNearest(r), scale);
+ g = AdvSimd.Multiply(AdvSimd.RoundToNearest(g), scale);
+ b = AdvSimd.Multiply(AdvSimd.RoundToNearest(b), scale);
+
+ c0 = r;
+ c1 = g;
+ c2 = b;
+ }
+ }
+
+ ///
+ public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane)
+ {
+ ref Vector128 destY =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0));
+ ref Vector128 destCb =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1));
+ ref Vector128 destCr =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2));
+
+ 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));
+
+ // Used for the color conversion
+ var chromaOffset = Vector128.Create(this.HalfValue);
+
+ var f0299 = Vector128.Create(0.299f);
+ var f0587 = Vector128.Create(0.587f);
+ var f0114 = Vector128.Create(0.114f);
+ var fn0168736 = Vector128.Create(-0.168736f);
+ var fn0331264 = Vector128.Create(-0.331264f);
+ var fn0418688 = Vector128.Create(-0.418688f);
+ var fn0081312F = Vector128.Create(-0.081312F);
+ var f05 = Vector128.Create(0.5f);
+
+ nuint n = (uint)values.Component0.Length / (uint)Vector128.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ Vector128 r = Unsafe.Add(ref srcR, i);
+ Vector128 g = Unsafe.Add(ref srcG, i);
+ Vector128 b = Unsafe.Add(ref srcB, i);
+
+ // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b)
+ // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b)
+ // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b)
+ Vector128 y = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f0114, b), f0587, g), f0299, r);
+ Vector128 cb = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(f05, b), fn0331264, g), fn0168736, r));
+ Vector128 cr = AdvSimd.Add(chromaOffset, HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(AdvSimd.Multiply(fn0081312F, b), fn0418688, g), f05, r));
+
+ Unsafe.Add(ref destY, i) = y;
+ Unsafe.Add(ref destCb, i) = cb;
+ Unsafe.Add(ref destCr, i) = cr;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs
index 0a2cfd084c..c5fa786e2c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrAvx.cs
@@ -38,7 +38,7 @@ internal abstract partial class JpegColorConverterBase
var bCbMult = Vector256.Create(YCbCrScalar.BCbMult);
// Walking 8 elements at one step:
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
@@ -98,7 +98,7 @@ internal abstract partial class JpegColorConverterBase
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
- nuint n = (uint)values.Component0.Length / (uint)Vector256.Count;
+ nuint n = values.Component0.Vector256Count();
for (nuint i = 0; i < n; i++)
{
Vector256 r = Unsafe.Add(ref srcR, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
index ca01aed612..a5d0c889e3 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector.cs
@@ -35,7 +35,7 @@ internal abstract partial class JpegColorConverterBase
var gCrMult = new Vector(-YCbCrScalar.GCrMult);
var bCbMult = new Vector(YCbCrScalar.BCbMult);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
// y = yVals[i];
@@ -103,7 +103,7 @@ internal abstract partial class JpegColorConverterBase
var gCrMult = new Vector(0.418688f);
var bCrMult = new Vector(0.081312f);
- nuint n = (uint)values.Component0.Length / (uint)Vector.Count;
+ nuint n = values.Component0.VectorCount();
for (nuint i = 0; i < n; i++)
{
Vector r = Unsafe.Add(ref srcR, i);
diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs
new file mode 100644
index 0000000000..285ba62cfd
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKArm64.cs
@@ -0,0 +1,133 @@
+// 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;
+using static SixLabors.ImageSharp.SimdUtils;
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
+
+internal abstract partial class JpegColorConverterBase
+{
+ internal sealed class YccKArm64 : JpegColorConverterArm64
+ {
+ public YccKArm64(int precision)
+ : base(JpegColorSpace.Ycck, 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 kBase =
+ ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3));
+
+ // Used for the color conversion
+ var chromaOffset = Vector128.Create(-this.HalfValue);
+ var scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue));
+ var max = Vector128.Create(this.MaximumValue);
+ var rCrMult = Vector128.Create(YCbCrScalar.RCrMult);
+ var gCbMult = Vector128.Create(-YCbCrScalar.GCbMult);
+ var gCrMult = Vector128.Create(-YCbCrScalar.GCrMult);
+ var bCbMult = Vector128.Create(YCbCrScalar.BCbMult);
+
+ // Walking 8 elements at one step:
+ nuint n = (uint)values.Component0.Length / (uint)Vector128.Count;
+ for (nuint i = 0; i < n; i++)
+ {
+ // y = yVals[i];
+ // cb = cbVals[i] - 128F;
+ // cr = crVals[i] - 128F;
+ // k = kVals[i] / 256F;
+ ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i);
+ ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i);
+ ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i);
+ Vector128