diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
deleted file mode 100644
index 663ee5062f..0000000000
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
-
-namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
-
-///
-/// Predicts an 8-bit AV1 block with the sample-domain midpoint when neither top nor left neighbors are available.
-///
-internal readonly struct Av1DcFillPredictor : IAv1Predictor
-{
- ///
- /// The number of samples written to each destination row.
- ///
- private readonly uint blockWidth;
-
- ///
- /// The number of destination rows.
- ///
- private readonly uint blockHeight;
-
- ///
- /// Initializes a new instance of the struct for explicit block dimensions.
- ///
- /// The predicted block dimensions in samples.
- public Av1DcFillPredictor(Size blockSize)
- {
- this.blockWidth = (uint)blockSize.Width;
- this.blockHeight = (uint)blockSize.Height;
- }
-
- ///
- /// Initializes a new instance of the struct for a transform size.
- ///
- /// The transform size whose dimensions define the predicted block.
- public Av1DcFillPredictor(Av1TransformSize transformSize)
- {
- this.blockWidth = (uint)transformSize.GetWidth();
- this.blockHeight = (uint)transformSize.GetHeight();
- }
-
- ///
- /// Predicts a transform block with the 8-bit midpoint value.
- ///
- /// The predicted block dimensions.
- /// The destination block.
- /// The distance, in samples, between destination rows.
- /// The unused top-neighbor buffer required by the common predictor signature.
- /// The unused left-neighbor buffer required by the common predictor signature.
- public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
- => new Av1DcFillPredictor(transformSize).PredictScalar(destination, stride, above, left);
-
- ///
- public void PredictScalar(Span destination, nuint stride, Span above, Span left)
- {
- // With no reference edge, AV1 uses the midpoint of the unsigned 8-bit sample domain as the DC predictor.
- const byte expectedDc = 0x80;
- Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
- Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
- ref byte destinationRef = ref destination[0];
- for (uint r = 0; r < this.blockHeight; r++)
- {
- Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
- destinationRef = ref Unsafe.Add(ref destinationRef, stride);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcIntraPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcIntraPredictor.cs
new file mode 100644
index 0000000000..b316bfed4c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcIntraPredictor.cs
@@ -0,0 +1,245 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+///
+/// Reconstructs AV1 DC intra-prediction blocks from the available neighboring samples.
+///
+internal static class Av1DcIntraPredictor
+{
+ ///
+ /// Predicts an 8-bit DC block.
+ ///
+ /// Whether the prepared left reference is available.
+ /// Whether the prepared top reference is available.
+ /// The destination block origin.
+ /// The destination row stride in samples.
+ /// The prepared top reference.
+ /// The prepared left reference.
+ /// The block width in samples.
+ /// The block height in samples.
+ public static void Predict(bool hasLeft, bool hasAbove, Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, int width, int height)
+ {
+ int count = (hasAbove ? width : 0) + (hasLeft ? height : 0);
+ int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0);
+
+ // Section 7.11.2.2 defines the unsigned midpoint when no reference is available. Otherwise adding half
+ // the reference count implements the specified rounded mean before extending it across the whole block.
+ byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count);
+ for (int row = 0; row < height; row++)
+ {
+ destination.Slice(row * destinationStride, width).Fill(prediction);
+ }
+ }
+
+ ///
+ /// Predicts a high-bit-depth DC block.
+ ///
+ /// Whether the prepared left reference is available.
+ /// Whether the prepared top reference is available.
+ /// The destination block origin.
+ /// The destination row stride in samples.
+ /// The prepared top reference.
+ /// The prepared left reference.
+ /// The block width in samples.
+ /// The block height in samples.
+ /// The reconstructed sample precision.
+ public static void Predict(bool hasLeft, bool hasAbove, Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, int width, int height, int bitDepth)
+ {
+ int count = (hasAbove ? width : 0) + (hasLeft ? height : 0);
+ int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0);
+ short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count);
+ for (int row = 0; row < height; row++)
+ {
+ destination.Slice(row * destinationStride, width).Fill(prediction);
+ }
+ }
+
+ ///
+ /// Predicts an 8-bit DC block without hardware intrinsics.
+ ///
+ /// Whether the prepared left reference is available.
+ /// Whether the prepared top reference is available.
+ /// The destination block origin.
+ /// The destination row stride in samples.
+ /// The prepared top reference.
+ /// The prepared left reference.
+ /// The block width in samples.
+ /// The block height in samples.
+ public static void PredictScalar(bool hasLeft, bool hasAbove, Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, int width, int height)
+ {
+ int count = (hasAbove ? width : 0) + (hasLeft ? height : 0);
+ int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0);
+ byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count);
+ for (int row = 0; row < height; row++)
+ {
+ ref byte destinationRow = ref destination[row * destinationStride];
+ for (int column = 0; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = prediction;
+ }
+ }
+ }
+
+ ///
+ /// Predicts a high-bit-depth DC block without hardware intrinsics.
+ ///
+ /// Whether the prepared left reference is available.
+ /// Whether the prepared top reference is available.
+ /// The destination block origin.
+ /// The destination row stride in samples.
+ /// The prepared top reference.
+ /// The prepared left reference.
+ /// The block width in samples.
+ /// The block height in samples.
+ /// The reconstructed sample precision.
+ public static void PredictScalar(bool hasLeft, bool hasAbove, Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, int width, int height, int bitDepth)
+ {
+ int count = (hasAbove ? width : 0) + (hasLeft ? height : 0);
+ int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0);
+ short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count);
+ for (int row = 0; row < height; row++)
+ {
+ ref short destinationRow = ref destination[row * destinationStride];
+ for (int column = 0; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = prediction;
+ }
+ }
+ }
+
+ ///
+ /// Sums 8-bit neighboring samples using the widest available SIMD width.
+ ///
+ /// The samples to sum.
+ /// The exact sum.
+ private static int Sum(ReadOnlySpan samples)
+ {
+ ref byte samplesBase = ref MemoryMarshal.GetReference(samples);
+ int sum = 0;
+ int index = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector512.Count;
+ for (; index <= oneVectorFromEnd; index += Vector512.Count)
+ {
+ (Vector512 low, Vector512 high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector512.Sum(low) + Vector512.Sum(high);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector256.Count;
+ for (; index <= oneVectorFromEnd; index += Vector256.Count)
+ {
+ (Vector256 low, Vector256 high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector256.Sum(low) + Vector256.Sum(high);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector128.Count;
+ for (; index <= oneVectorFromEnd; index += Vector128.Count)
+ {
+ (Vector128 low, Vector128 high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector128.Sum(low) + Vector128.Sum(high);
+ }
+ }
+
+ for (; index < samples.Length; index++)
+ {
+ sum += Unsafe.Add(ref samplesBase, index);
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Sums high-bit-depth neighboring samples using the widest available SIMD width.
+ ///
+ /// The samples to sum.
+ /// The exact sum.
+ private static int Sum(ReadOnlySpan samples)
+ {
+ ref short samplesBase = ref MemoryMarshal.GetReference(samples);
+ int sum = 0;
+ int index = 0;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector512.Count;
+ for (; index <= oneVectorFromEnd; index += Vector512.Count)
+ {
+ (Vector512 low, Vector512 high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector512.Sum(low) + Vector512.Sum(high);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector256.Count;
+ for (; index <= oneVectorFromEnd; index += Vector256.Count)
+ {
+ (Vector256 low, Vector256 high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector256.Sum(low) + Vector256.Sum(high);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = samples.Length - Vector128.Count;
+ for (; index <= oneVectorFromEnd; index += Vector128.Count)
+ {
+ (Vector128 low, Vector128 high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index));
+ sum += Vector128.Sum(low) + Vector128.Sum(high);
+ }
+ }
+
+ for (; index < samples.Length; index++)
+ {
+ sum += Unsafe.Add(ref samplesBase, index);
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Sums 8-bit neighboring samples without hardware intrinsics.
+ ///
+ /// The samples to sum.
+ /// The exact sum.
+ private static int SumScalar(ReadOnlySpan samples)
+ {
+ int sum = 0;
+ foreach (byte sample in samples)
+ {
+ sum += sample;
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Sums high-bit-depth neighboring samples without hardware intrinsics.
+ ///
+ /// The samples to sum.
+ /// The exact sum.
+ private static int SumScalar(ReadOnlySpan samples)
+ {
+ int sum = 0;
+ foreach (short sample in samples)
+ {
+ sum += sample;
+ }
+
+ return sum;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
deleted file mode 100644
index d2ffbbaca8..0000000000
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
-
-namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
-
-///
-/// Predicts an 8-bit AV1 block from the rounded average of its available left neighboring samples.
-///
-internal readonly struct Av1DcLeftPredictor : IAv1Predictor
-{
- ///
- /// The number of samples written to each destination row.
- ///
- private readonly uint blockWidth;
-
- ///
- /// The number of left samples averaged and destination rows written.
- ///
- private readonly uint blockHeight;
-
- ///
- /// Initializes a new instance of the struct for explicit block dimensions.
- ///
- /// The predicted block dimensions in samples.
- public Av1DcLeftPredictor(Size blockSize)
- {
- this.blockWidth = (uint)blockSize.Width;
- this.blockHeight = (uint)blockSize.Height;
- }
-
- ///
- /// Initializes a new instance of the struct for a transform size.
- ///
- /// The transform size whose dimensions define the predicted block.
- public Av1DcLeftPredictor(Av1TransformSize transformSize)
- {
- this.blockWidth = (uint)transformSize.GetWidth();
- this.blockHeight = (uint)transformSize.GetHeight();
- }
-
- ///
- /// Predicts a transform block from its left neighboring samples.
- ///
- /// The predicted block dimensions.
- /// The destination block.
- /// The distance, in samples, between destination rows.
- /// The unused top-neighbor buffer required by the common predictor signature.
- /// The left neighboring samples.
- public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
- => new Av1DcLeftPredictor(transformSize).PredictScalar(destination, stride, above, left);
-
- ///
- public void PredictScalar(Span destination, nuint stride, Span above, Span left)
- {
- int sum = 0;
- Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
- Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
- Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
- ref byte leftRef = ref left[0];
- ref byte destinationRef = ref destination[0];
- for (uint i = 0; i < this.blockHeight; i++)
- {
- sum += Unsafe.Add(ref leftRef, i);
- }
-
- // Adding half the sample count implements the normative nearest-integer DC average before division.
- byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight);
- for (uint r = 0; r < this.blockHeight; r++)
- {
- Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
- destinationRef = ref Unsafe.Add(ref destinationRef, stride);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
deleted file mode 100644
index 220975b65c..0000000000
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
-
-namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
-
-///
-/// Predicts an 8-bit AV1 block from the rounded average of its top and left neighboring samples.
-///
-internal readonly struct Av1DcPredictor : IAv1Predictor
-{
- ///
- /// The number of top samples averaged and samples written to each destination row.
- ///
- private readonly nuint blockWidth;
-
- ///
- /// The number of left samples averaged and destination rows written.
- ///
- private readonly nuint blockHeight;
-
- ///
- /// Initializes a new instance of the struct for explicit block dimensions.
- ///
- /// The predicted block dimensions in samples.
- public Av1DcPredictor(Size blockSize)
- {
- this.blockWidth = (nuint)blockSize.Width;
- this.blockHeight = (nuint)blockSize.Height;
- }
-
- ///
- /// Initializes a new instance of the struct for a transform size.
- ///
- /// The transform size whose dimensions define the predicted block.
- public Av1DcPredictor(Av1TransformSize transformSize)
- {
- this.blockWidth = (nuint)transformSize.GetWidth();
- this.blockHeight = (nuint)transformSize.GetHeight();
- }
-
- ///
- /// Predicts a transform block from its top and left neighboring samples.
- ///
- /// The predicted block dimensions.
- /// The destination block.
- /// The distance, in samples, between destination rows.
- /// The top neighboring samples.
- /// The left neighboring samples.
- public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
- => new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left);
-
- ///
- public void PredictScalar(Span destination, nuint stride, Span above, Span left)
- {
- int sum = 0;
- Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
- Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left));
- Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
- Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
- ref byte leftRef = ref left[0];
- ref byte aboveRef = ref above[0];
- ref byte destinationRef = ref destination[0];
- uint count = (uint)(this.blockWidth + this.blockHeight);
- uint width = (uint)this.blockWidth;
- for (nuint i = 0; i < this.blockWidth; i++)
- {
- sum += Unsafe.Add(ref aboveRef, i);
- }
-
- for (nuint i = 0; i < this.blockHeight; i++)
- {
- sum += Unsafe.Add(ref leftRef, i);
- }
-
- // Adding half the combined edge count implements the normative nearest-integer DC average before division.
- byte expectedDc = (byte)((sum + (count >> 1)) / count);
- for (nuint r = 0; r < this.blockHeight; r++)
- {
- Unsafe.InitBlock(ref destinationRef, expectedDc, width);
- destinationRef = ref Unsafe.Add(ref destinationRef, stride);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
deleted file mode 100644
index a8e41b2cb4..0000000000
--- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
-
-namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
-
-///
-/// Predicts an 8-bit AV1 block from the rounded average of its available top neighboring samples.
-///
-internal readonly struct Av1DcTopPredictor : IAv1Predictor
-{
- ///
- /// The number of top samples averaged and samples written to each destination row.
- ///
- private readonly uint blockWidth;
-
- ///
- /// The number of destination rows.
- ///
- private readonly uint blockHeight;
-
- ///
- /// Initializes a new instance of the struct for explicit block dimensions.
- ///
- /// The predicted block dimensions in samples.
- public Av1DcTopPredictor(Size blockSize)
- {
- this.blockWidth = (uint)blockSize.Width;
- this.blockHeight = (uint)blockSize.Height;
- }
-
- ///
- /// Initializes a new instance of the struct for a transform size.
- ///
- /// The transform size whose dimensions define the predicted block.
- public Av1DcTopPredictor(Av1TransformSize transformSize)
- {
- this.blockWidth = (uint)transformSize.GetWidth();
- this.blockHeight = (uint)transformSize.GetHeight();
- }
-
- ///
- /// Predicts a transform block from its top neighboring samples.
- ///
- /// The predicted block dimensions.
- /// The destination block.
- /// The distance, in samples, between destination rows.
- /// The top neighboring samples.
- /// The unused left-neighbor buffer required by the common predictor signature.
- public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left)
- => new Av1DcTopPredictor(transformSize).PredictScalar(destination, stride, above, left);
-
- ///
- public void PredictScalar(Span destination, nuint stride, Span above, Span left)
- {
- int sum = 0;
- Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
- Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above));
- Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
- ref byte aboveRef = ref above[0];
- ref byte destinationRef = ref destination[0];
- for (uint i = 0; i < this.blockWidth; i++)
- {
- sum += Unsafe.Add(ref aboveRef, i);
- }
-
- // Adding half the sample count implements the normative nearest-integer DC average before division.
- byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth);
- for (uint r = 0; r < this.blockHeight; r++)
- {
- Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
- destinationRef = ref Unsafe.Add(ref destinationRef, stride);
- }
- }
-}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Simd.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Simd.cs
new file mode 100644
index 0000000000..eb4ed15c6b
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Simd.cs
@@ -0,0 +1,619 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+///
+/// Provides the SIMD projection kernels for AV1 directional intra prediction.
+///
+internal static partial class Av1DirectionalIntraPredictor
+{
+ ///
+ /// Gets the indices of even bytes in one upsampled reference vector.
+ ///
+ private static Vector128 EvenByteIndices => Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14);
+
+ ///
+ /// Gets the indices of odd bytes in one upsampled reference vector.
+ ///
+ private static Vector128 OddByteIndices => Vector128.Create((byte)1, 3, 5, 7, 9, 11, 13, 15, 1, 3, 5, 7, 9, 11, 13, 15);
+
+ ///
+ /// Gets the indices of even high-bit-depth samples in one upsampled reference vector.
+ ///
+ private static Vector128 EvenShortIndices => Vector128.Create((short)0, 2, 4, 6, 0, 2, 4, 6);
+
+ ///
+ /// Gets the indices of odd high-bit-depth samples in one upsampled reference vector.
+ ///
+ private static Vector128 OddShortIndices => Vector128.Create((short)1, 3, 5, 7, 1, 3, 5, 7);
+
+ ///
+ /// Predicts one 8-bit zone 1 block using contiguous SIMD projection rows.
+ ///
+ /// The destination block origin.
+ /// The destination row stride.
+ /// The projected top reference.
+ /// Whether the reference contains half-sample positions.
+ /// The Q8 projection derivative.
+ /// The block width.
+ /// The block height.
+ private static void PredictZone1(Span destination, int destinationStride, ReadOnlySpan above, bool upsample, int derivative, int width, int height)
+ {
+ int upsampleShift = upsample ? 1 : 0;
+ int maximumBasis = (width + height - 1) << upsampleShift;
+ int fractionBits = 6 - upsampleShift;
+ int projection = derivative;
+
+ for (int row = 0; row < height; row++, projection += derivative)
+ {
+ int basis = projection >> fractionBits;
+ int weight = ((projection << upsampleShift) & 0x3F) >> 1;
+ InterpolateRow(destination.Slice(row * destinationStride, width), above, basis, weight, upsample, maximumBasis);
+ }
+ }
+
+ ///
+ /// Predicts one high-bit-depth zone 1 block using contiguous SIMD projection rows.
+ ///
+ /// The destination block origin.
+ /// The destination row stride.
+ /// The projected top reference.
+ /// Whether the reference contains half-sample positions.
+ /// The Q8 projection derivative.
+ /// The block width.
+ /// The block height.
+ private static void PredictZone1(Span destination, int destinationStride, ReadOnlySpan above, bool upsample, int derivative, int width, int height)
+ {
+ int upsampleShift = upsample ? 1 : 0;
+ int maximumBasis = (width + height - 1) << upsampleShift;
+ int fractionBits = 6 - upsampleShift;
+ int projection = derivative;
+
+ for (int row = 0; row < height; row++, projection += derivative)
+ {
+ int basis = projection >> fractionBits;
+ int weight = ((projection << upsampleShift) & 0x3F) >> 1;
+ InterpolateRow(destination.Slice(row * destinationStride, width), above, basis, weight, upsample, maximumBasis);
+ }
+ }
+
+ ///
+ /// Predicts one 8-bit zone 2 block using vectorized left gathers and contiguous top projection rows.
+ ///
+ /// The destination block origin.
+ /// The destination row stride.
+ /// The projected top reference.
+ /// The projected left reference.
+ /// Whether the top reference contains half-sample positions.
+ /// Whether the left reference contains half-sample positions.
+ /// The horizontal Q8 derivative.
+ /// The vertical Q8 derivative.
+ /// The block width.
+ /// The block height.
+ private static void PredictZone2(Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height)
+ {
+ int aboveShift = upsampleAbove ? 1 : 0;
+ int minimumTopBasis = -(1 << aboveShift);
+ int topFractionBits = 6 - aboveShift;
+ int topBasisIncrement = 1 << aboveShift;
+ int topProjection = -dx;
+
+ for (int row = 0; row < height; row++, topProjection -= dx)
+ {
+ int topBasis = topProjection >> topFractionBits;
+ int leftCount = 0;
+ while (leftCount < width && topBasis < minimumTopBasis)
+ {
+ leftCount++;
+ topBasis += topBasisIncrement;
+ }
+
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ int leftProjection = (row << 6) - dy;
+ InterpolateLeft(destinationRow[..leftCount], left, leftProjection, dy, upsampleLeft);
+
+ if (leftCount < width)
+ {
+ int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1;
+ InterpolateRow(destinationRow[leftCount..], above, topBasis, topWeight, upsampleAbove, int.MaxValue);
+ }
+ }
+ }
+
+ ///
+ /// Predicts one high-bit-depth zone 2 block using vectorized left gathers and contiguous top projection rows.
+ ///
+ /// The destination block origin.
+ /// The destination row stride.
+ /// The projected top reference.
+ /// The projected left reference.
+ /// Whether the top reference contains half-sample positions.
+ /// Whether the left reference contains half-sample positions.
+ /// The horizontal Q8 derivative.
+ /// The vertical Q8 derivative.
+ /// The block width.
+ /// The block height.
+ private static void PredictZone2(Span destination, int destinationStride, ReadOnlySpan above, ReadOnlySpan left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height)
+ {
+ int aboveShift = upsampleAbove ? 1 : 0;
+ int minimumTopBasis = -(1 << aboveShift);
+ int topFractionBits = 6 - aboveShift;
+ int topBasisIncrement = 1 << aboveShift;
+ int topProjection = -dx;
+
+ for (int row = 0; row < height; row++, topProjection -= dx)
+ {
+ int topBasis = topProjection >> topFractionBits;
+ int leftCount = 0;
+ while (leftCount < width && topBasis < minimumTopBasis)
+ {
+ leftCount++;
+ topBasis += topBasisIncrement;
+ }
+
+ Span destinationRow = destination.Slice(row * destinationStride, width);
+ int leftProjection = (row << 6) - dy;
+ InterpolateLeft(destinationRow[..leftCount], left, leftProjection, dy, upsampleLeft);
+
+ if (leftCount < width)
+ {
+ int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1;
+ InterpolateRow(destinationRow[leftCount..], above, topBasis, topWeight, upsampleAbove, int.MaxValue);
+ }
+ }
+ }
+
+ ///
+ /// Interpolates one 8-bit projection row.
+ ///
+ /// The destination row.
+ /// The projected reference samples.
+ /// The first integral reference coordinate.
+ /// The right-sample interpolation weight.
+ /// Whether consecutive output samples advance two reference positions.
+ /// The final extended reference coordinate, or when the row cannot reach it.
+ private static void InterpolateRow(Span destination, ReadOnlySpan reference, int basis, int weight, bool upsample, int maximumBasis)
+ {
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref byte referenceBase = ref MemoryMarshal.GetReference(reference);
+ int basisIncrement = upsample ? 2 : 1;
+ int validCount = maximumBasis == int.MaxValue || basis >= maximumBasis
+ ? maximumBasis == int.MaxValue ? destination.Length : 0
+ : Math.Min(destination.Length, ((maximumBasis - 1 - basis) / basisIncrement) + 1);
+ int index = 0;
+
+ if (!upsample)
+ {
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector512.Count;
+ for (; index <= oneVectorFromEnd; index += Vector512.Count)
+ {
+ Vector512 left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector512 right = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector256.Count;
+ for (; index <= oneVectorFromEnd; index += Vector256.Count)
+ {
+ Vector256 left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector256 right = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector128.Count;
+ for (; index <= oneVectorFromEnd; index += Vector128.Count)
+ {
+ Vector128 left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector128 right = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+ }
+ else if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - 8;
+ for (; index <= oneVectorFromEnd; index += 8)
+ {
+ Vector128 source = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + (index * 2)));
+
+ // ShuffleNative maps to byte-table lookup on AdvSimd and PSHUFB on x86. Eight output samples are
+ // gathered from sixteen half-sample positions without scalar lane construction.
+ Vector128 left = Vector128.ShuffleNative(source, EvenByteIndices);
+ Vector128 right = Vector128.ShuffleNative(source, OddByteIndices);
+ Vector128 prediction = Interpolate(left, right, weight);
+ Unsafe.As(ref Unsafe.Add(ref destinationBase, index)) = prediction.AsUInt64().GetElement(0);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - 4;
+ for (; index <= oneVectorFromEnd; index += 4)
+ {
+ int source = basis + (index * basisIncrement);
+ Vector128 left = Vector128.Create(
+ (int)Unsafe.Add(ref referenceBase, source),
+ Unsafe.Add(ref referenceBase, source + basisIncrement),
+ Unsafe.Add(ref referenceBase, source + (2 * basisIncrement)),
+ Unsafe.Add(ref referenceBase, source + (3 * basisIncrement)));
+
+ Vector128 right = Vector128.Create(
+ (int)Unsafe.Add(ref referenceBase, source + 1),
+ Unsafe.Add(ref referenceBase, source + basisIncrement + 1),
+ Unsafe.Add(ref referenceBase, source + (2 * basisIncrement) + 1),
+ Unsafe.Add(ref referenceBase, source + (3 * basisIncrement) + 1));
+
+ StoreFourBytes(Interpolate(left, right, Vector128.Create(weight)), ref Unsafe.Add(ref destinationBase, index));
+ }
+ }
+
+ for (; index < validCount; index++)
+ {
+ int source = basis + (index * basisIncrement);
+ Unsafe.Add(ref destinationBase, index) = (byte)(((Unsafe.Add(ref referenceBase, source) * (32 - weight)) + (Unsafe.Add(ref referenceBase, source + 1) * weight) + 16) >> 5);
+ }
+
+ if (validCount < destination.Length)
+ {
+ destination[validCount..].Fill(Unsafe.Add(ref referenceBase, maximumBasis));
+ }
+ }
+
+ ///
+ /// Interpolates one high-bit-depth projection row.
+ ///
+ /// The destination row.
+ /// The projected reference samples.
+ /// The first integral reference coordinate.
+ /// The right-sample interpolation weight.
+ /// Whether consecutive output samples advance two reference positions.
+ /// The final extended reference coordinate, or when the row cannot reach it.
+ private static void InterpolateRow(Span destination, ReadOnlySpan reference, int basis, int weight, bool upsample, int maximumBasis)
+ {
+ ref short destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short referenceBase = ref MemoryMarshal.GetReference(reference);
+ int basisIncrement = upsample ? 2 : 1;
+ int validCount = maximumBasis == int.MaxValue || basis >= maximumBasis
+ ? maximumBasis == int.MaxValue ? destination.Length : 0
+ : Math.Min(destination.Length, ((maximumBasis - 1 - basis) / basisIncrement) + 1);
+ int index = 0;
+
+ if (!upsample)
+ {
+ if (Vector512.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector512.Count;
+ for (; index <= oneVectorFromEnd; index += Vector512.Count)
+ {
+ Vector512 left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector512 right = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector256.Count;
+ for (; index <= oneVectorFromEnd; index += Vector256.Count)
+ {
+ Vector256 left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector256 right = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - Vector128.Count;
+ for (; index <= oneVectorFromEnd; index += Vector128.Count)
+ {
+ Vector128 left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
+ Vector128 right = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1));
+ Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index);
+ }
+ }
+ }
+ else if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - 4;
+ for (; index <= oneVectorFromEnd; index += 4)
+ {
+ Vector128 source = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + (index * 2)));
+ Vector128 left = Vector128.ShuffleNative(source, EvenShortIndices);
+ Vector128 right = Vector128.ShuffleNative(source, OddShortIndices);
+ Vector128 prediction = Interpolate(left, right, weight);
+ Unsafe.As(ref Unsafe.Add(ref destinationBase, index)) = prediction.AsUInt64().GetElement(0);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = validCount - 4;
+ for (; index <= oneVectorFromEnd; index += 4)
+ {
+ int source = basis + (index * basisIncrement);
+ Vector128 left = Vector128.Create(
+ (int)Unsafe.Add(ref referenceBase, source),
+ Unsafe.Add(ref referenceBase, source + basisIncrement),
+ Unsafe.Add(ref referenceBase, source + (2 * basisIncrement)),
+ Unsafe.Add(ref referenceBase, source + (3 * basisIncrement)));
+
+ Vector128 right = Vector128.Create(
+ (int)Unsafe.Add(ref referenceBase, source + 1),
+ Unsafe.Add(ref referenceBase, source + basisIncrement + 1),
+ Unsafe.Add(ref referenceBase, source + (2 * basisIncrement) + 1),
+ Unsafe.Add(ref referenceBase, source + (3 * basisIncrement) + 1));
+
+ StoreFourShorts(Interpolate(left, right, Vector128.Create(weight)), ref Unsafe.Add(ref destinationBase, index));
+ }
+ }
+
+ for (; index < validCount; index++)
+ {
+ int source = basis + (index * basisIncrement);
+ Unsafe.Add(ref destinationBase, index) = (short)(((Unsafe.Add(ref referenceBase, source) * (32 - weight)) + (Unsafe.Add(ref referenceBase, source + 1) * weight) + 16) >> 5);
+ }
+
+ if (validCount < destination.Length)
+ {
+ destination[validCount..].Fill(Unsafe.Add(ref referenceBase, maximumBasis));
+ }
+ }
+
+ ///
+ /// Interpolates the left-edge prefix of one 8-bit zone 2 row.
+ ///
+ /// The destination prefix.
+ /// The projected left reference.
+ /// The first Q6 left projection.
+ /// The Q8 derivative subtracted between columns.
+ /// Whether the left reference contains half-sample positions.
+ private static void InterpolateLeft(Span destination, ReadOnlySpan left, int projection, int derivative, bool upsample)
+ {
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref byte leftBase = ref MemoryMarshal.GetReference(left);
+ int upsampleShift = upsample ? 1 : 0;
+ int fractionBits = 6 - upsampleShift;
+ int index = 0;
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = destination.Length - 4;
+ for (; index <= oneVectorFromEnd; index += 4)
+ {
+ int projection0 = projection - (index * derivative);
+ int projection1 = projection0 - derivative;
+ int projection2 = projection1 - derivative;
+ int projection3 = projection2 - derivative;
+ int basis0 = projection0 >> fractionBits;
+ int basis1 = projection1 >> fractionBits;
+ int basis2 = projection2 >> fractionBits;
+ int basis3 = projection3 >> fractionBits;
+ Vector128 source0 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0), Unsafe.Add(ref leftBase, basis1), Unsafe.Add(ref leftBase, basis2), Unsafe.Add(ref leftBase, basis3));
+ Vector128 source1 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0 + 1), Unsafe.Add(ref leftBase, basis1 + 1), Unsafe.Add(ref leftBase, basis2 + 1), Unsafe.Add(ref leftBase, basis3 + 1));
+ Vector128 weights = Vector128.Create(
+ ((projection0 << upsampleShift) & 0x3F) >> 1,
+ ((projection1 << upsampleShift) & 0x3F) >> 1,
+ ((projection2 << upsampleShift) & 0x3F) >> 1,
+ ((projection3 << upsampleShift) & 0x3F) >> 1);
+
+ StoreFourBytes(Interpolate(source0, source1, weights), ref Unsafe.Add(ref destinationBase, index));
+ }
+ }
+
+ for (; index < destination.Length; index++)
+ {
+ int currentProjection = projection - (index * derivative);
+ int basis = currentProjection >> fractionBits;
+ int weight = ((currentProjection << upsampleShift) & 0x3F) >> 1;
+ Unsafe.Add(ref destinationBase, index) = (byte)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5);
+ }
+ }
+
+ ///
+ /// Interpolates the left-edge prefix of one high-bit-depth zone 2 row.
+ ///
+ /// The destination prefix.
+ /// The projected left reference.
+ /// The first Q6 left projection.
+ /// The Q8 derivative subtracted between columns.
+ /// Whether the left reference contains half-sample positions.
+ private static void InterpolateLeft(Span destination, ReadOnlySpan left, int projection, int derivative, bool upsample)
+ {
+ ref short destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short leftBase = ref MemoryMarshal.GetReference(left);
+ int upsampleShift = upsample ? 1 : 0;
+ int fractionBits = 6 - upsampleShift;
+ int index = 0;
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int oneVectorFromEnd = destination.Length - 4;
+ for (; index <= oneVectorFromEnd; index += 4)
+ {
+ int projection0 = projection - (index * derivative);
+ int projection1 = projection0 - derivative;
+ int projection2 = projection1 - derivative;
+ int projection3 = projection2 - derivative;
+ int basis0 = projection0 >> fractionBits;
+ int basis1 = projection1 >> fractionBits;
+ int basis2 = projection2 >> fractionBits;
+ int basis3 = projection3 >> fractionBits;
+ Vector128 source0 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0), Unsafe.Add(ref leftBase, basis1), Unsafe.Add(ref leftBase, basis2), Unsafe.Add(ref leftBase, basis3));
+ Vector128 source1 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0 + 1), Unsafe.Add(ref leftBase, basis1 + 1), Unsafe.Add(ref leftBase, basis2 + 1), Unsafe.Add(ref leftBase, basis3 + 1));
+ Vector128 weights = Vector128.Create(
+ ((projection0 << upsampleShift) & 0x3F) >> 1,
+ ((projection1 << upsampleShift) & 0x3F) >> 1,
+ ((projection2 << upsampleShift) & 0x3F) >> 1,
+ ((projection3 << upsampleShift) & 0x3F) >> 1);
+
+ StoreFourShorts(Interpolate(source0, source1, weights), ref Unsafe.Add(ref destinationBase, index));
+ }
+ }
+
+ for (; index < destination.Length; index++)
+ {
+ int currentProjection = projection - (index * derivative);
+ int basis = currentProjection >> fractionBits;
+ int weight = ((currentProjection << upsampleShift) & 0x3F) >> 1;
+ Unsafe.Add(ref destinationBase, index) = (short)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5);
+ }
+ }
+
+ ///
+ /// Interpolates sixty-four pairs of 8-bit references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 Interpolate(Vector512 left, Vector512 right, int weight)
+ {
+ (Vector512 leftLow, Vector512 leftHigh) = Vector512.Widen(left);
+ (Vector512 rightLow, Vector512 rightHigh) = Vector512.Widen(right);
+ Vector512 rounding = Vector512.Create((ushort)16);
+ Vector512 low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
+ Vector512 high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
+ return Vector512.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates thirty-two pairs of 8-bit references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 Interpolate(Vector256 left, Vector256 right, int weight)
+ {
+ (Vector256 leftLow, Vector256 leftHigh) = Vector256.Widen(left);
+ (Vector256 rightLow, Vector256 rightHigh) = Vector256.Widen(right);
+ Vector256 rounding = Vector256.Create((ushort)16);
+ Vector256 low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
+ Vector256 high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
+ return Vector256.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates sixteen pairs of 8-bit references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Interpolate(Vector128 left, Vector128 right, int weight)
+ {
+ (Vector128 leftLow, Vector128 leftHigh) = Vector128.Widen(left);
+ (Vector128 rightLow, Vector128 rightHigh) = Vector128.Widen(right);
+ Vector128 rounding = Vector128.Create((ushort)16);
+ Vector128 low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
+ Vector128 high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
+ return Vector128.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates thirty-two pairs of high-bit-depth references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 Interpolate(Vector512 left, Vector512 right, int weight)
+ {
+ (Vector512 leftLow, Vector512 leftHigh) = Vector512.Widen(left);
+ (Vector512 rightLow, Vector512 rightHigh) = Vector512.Widen(right);
+ Vector512 rounding = Vector512.Create(16);
+ Vector512 low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
+ Vector512 high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
+ return Vector512.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates sixteen pairs of high-bit-depth references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 Interpolate(Vector256 left, Vector256 right, int weight)
+ {
+ (Vector256 leftLow, Vector256 leftHigh) = Vector256.Widen(left);
+ (Vector256 rightLow, Vector256 rightHigh) = Vector256.Widen(right);
+ Vector256 rounding = Vector256.Create(16);
+ Vector256 low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
+ Vector256 high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
+ return Vector256.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates eight pairs of high-bit-depth references.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weight.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Interpolate(Vector128 left, Vector128 right, int weight)
+ {
+ (Vector128 leftLow, Vector128 leftHigh) = Vector128.Widen(left);
+ (Vector128 rightLow, Vector128 rightHigh) = Vector128.Widen(right);
+ Vector128 rounding = Vector128.Create(16);
+ Vector128 low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
+ Vector128 high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
+ return Vector128.Narrow(low, high);
+ }
+
+ ///
+ /// Interpolates four widened reference pairs with independent weights.
+ ///
+ /// The left interpolation samples.
+ /// The right interpolation samples.
+ /// The right-sample weights.
+ /// The rounded interpolated samples.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 Interpolate(Vector128 left, Vector128 right, Vector128 weights)
+ => ((left * (Vector128.Create(32) - weights)) + (right * weights) + Vector128.Create(16)) >> 5;
+
+ ///
+ /// Stores four widened predictions as packed 8-bit samples.
+ ///
+ /// The widened predictions.
+ /// The first destination sample.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void StoreFourBytes(Vector128 prediction, ref byte destination)
+ {
+ Vector128 narrowed16 = Vector128.Narrow(prediction.AsUInt32(), Vector128.Zero);
+ Vector128 narrowed8 = Vector128.Narrow(narrowed16, Vector128.Zero);
+ Unsafe.As(ref destination) = narrowed8.AsUInt32().GetElement(0);
+ }
+
+ ///
+ /// Stores four widened predictions as packed high-bit-depth samples.
+ ///
+ /// The widened predictions.
+ /// The first destination sample.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void StoreFourShorts(Vector128 prediction, ref short destination)
+ {
+ Vector128 narrowed = Vector128.Narrow(prediction, Vector128.Zero);
+ Unsafe.As(ref destination) = narrowed.AsUInt64().GetElement(0);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Transpose.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Transpose.cs
new file mode 100644
index 0000000000..c980d833f1
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Transpose.cs
@@ -0,0 +1,313 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using SixLabors.ImageSharp.Common.Helpers;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
+
+///
+/// Provides SIMD block transposition for zone 3 directional prediction.
+///
+internal static partial class Av1DirectionalIntraPredictor
+{
+ ///
+ /// Transposes a contiguous 8-bit prediction block into the reconstructed destination.
+ ///
+ /// The contiguous source block.
+ /// The destination block origin.
+ /// The source row width.
+ /// The number of source rows.
+ /// The destination row stride.
+ private static void Transpose(ReadOnlySpan source, Span destination, int sourceWidth, int sourceHeight, int destinationStride)
+ {
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int tileSize = sourceWidth >= 8 && sourceHeight >= 8 ? 8 : 4;
+ for (int y = 0; y < sourceHeight; y += tileSize)
+ {
+ for (int x = 0; x < sourceWidth; x += tileSize)
+ {
+ if (tileSize == 8)
+ {
+ Transpose8x8(source, destination, sourceWidth, destinationStride, x, y);
+ }
+ else
+ {
+ Transpose4x4(source, destination, sourceWidth, destinationStride, x, y);
+ }
+ }
+ }
+
+ return;
+ }
+
+ for (int y = 0; y < sourceHeight; y++)
+ {
+ for (int x = 0; x < sourceWidth; x++)
+ {
+ destination[(x * destinationStride) + y] = source[(y * sourceWidth) + x];
+ }
+ }
+ }
+
+ ///
+ /// Transposes a contiguous high-bit-depth prediction block into the reconstructed destination.
+ ///
+ /// The contiguous source block.
+ /// The destination block origin.
+ /// The source row width.
+ /// The number of source rows.
+ /// The destination row stride.
+ private static void Transpose(ReadOnlySpan source, Span destination, int sourceWidth, int sourceHeight, int destinationStride)
+ {
+ if (Vector128.IsHardwareAccelerated)
+ {
+ int tileSize = sourceWidth >= 8 && sourceHeight >= 8 ? 8 : 4;
+ for (int y = 0; y < sourceHeight; y += tileSize)
+ {
+ for (int x = 0; x < sourceWidth; x += tileSize)
+ {
+ if (tileSize == 8)
+ {
+ Transpose8x8(source, destination, sourceWidth, destinationStride, x, y);
+ }
+ else
+ {
+ Transpose4x4(source, destination, sourceWidth, destinationStride, x, y);
+ }
+ }
+ }
+
+ return;
+ }
+
+ for (int y = 0; y < sourceHeight; y++)
+ {
+ for (int x = 0; x < sourceWidth; x++)
+ {
+ destination[(x * destinationStride) + y] = source[(y * sourceWidth) + x];
+ }
+ }
+ }
+
+ ///
+ /// Transposes one eight-by-eight tile of 8-bit prediction samples.
+ ///
+ /// The contiguous source block.
+ /// The destination block origin.
+ /// The source row stride.
+ /// The destination row stride.
+ /// The tile X coordinate.
+ /// The tile Y coordinate.
+ private static void Transpose8x8(ReadOnlySpan source, Span destination, int sourceStride, int destinationStride, int x, int y)
+ {
+ ref byte sourceBase = ref MemoryMarshal.GetReference(source);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ Vector128 row0 = LoadEightBytes(ref sourceBase, ((y + 0) * sourceStride) + x);
+ Vector128 row1 = LoadEightBytes(ref sourceBase, ((y + 1) * sourceStride) + x);
+ Vector128 row2 = LoadEightBytes(ref sourceBase, ((y + 2) * sourceStride) + x);
+ Vector128 row3 = LoadEightBytes(ref sourceBase, ((y + 3) * sourceStride) + x);
+ Vector128 row4 = LoadEightBytes(ref sourceBase, ((y + 4) * sourceStride) + x);
+ Vector128 row5 = LoadEightBytes(ref sourceBase, ((y + 5) * sourceStride) + x);
+ Vector128 row6 = LoadEightBytes(ref sourceBase, ((y + 6) * sourceStride) + x);
+ Vector128 row7 = LoadEightBytes(ref sourceBase, ((y + 7) * sourceStride) + x);
+
+ // Three unpack stages exchange one, two, then four byte coordinates. Each final vector contains two
+ // complete source columns, which are written as two contiguous eight-byte destination rows.
+ Vector128 pair0 = Vector128_.UnpackLow(row0, row1);
+ Vector128 pair2 = Vector128_.UnpackLow(row2, row3);
+ Vector128 pair4 = Vector128_.UnpackLow(row4, row5);
+ Vector128 pair6 = Vector128_.UnpackLow(row6, row7);
+ Vector128 quad0 = Vector128_.UnpackLow(pair0.AsInt16(), pair2.AsInt16());
+ Vector128 quad1 = Vector128_.UnpackHigh(pair0.AsInt16(), pair2.AsInt16());
+ Vector128 quad4 = Vector128_.UnpackLow(pair4.AsInt16(), pair6.AsInt16());
+ Vector128 quad5 = Vector128_.UnpackHigh(pair4.AsInt16(), pair6.AsInt16());
+ Vector128 columns01 = Vector128_.UnpackLow(quad0.AsInt32(), quad4.AsInt32());
+ Vector128 columns23 = Vector128_.UnpackHigh(quad0.AsInt32(), quad4.AsInt32());
+ Vector128 columns45 = Vector128_.UnpackLow(quad1.AsInt32(), quad5.AsInt32());
+ Vector128 columns67 = Vector128_.UnpackHigh(quad1.AsInt32(), quad5.AsInt32());
+
+ StoreEightBytes(columns01.AsUInt64().GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y);
+ StoreEightBytes(columns01.AsUInt64().GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y);
+ StoreEightBytes(columns23.AsUInt64().GetElement(0), ref destinationBase, ((x + 2) * destinationStride) + y);
+ StoreEightBytes(columns23.AsUInt64().GetElement(1), ref destinationBase, ((x + 3) * destinationStride) + y);
+ StoreEightBytes(columns45.AsUInt64().GetElement(0), ref destinationBase, ((x + 4) * destinationStride) + y);
+ StoreEightBytes(columns45.AsUInt64().GetElement(1), ref destinationBase, ((x + 5) * destinationStride) + y);
+ StoreEightBytes(columns67.AsUInt64().GetElement(0), ref destinationBase, ((x + 6) * destinationStride) + y);
+ StoreEightBytes(columns67.AsUInt64().GetElement(1), ref destinationBase, ((x + 7) * destinationStride) + y);
+ }
+
+ ///
+ /// Transposes one four-by-four tile of 8-bit prediction samples.
+ ///
+ /// The contiguous source block.
+ /// The destination block origin.
+ /// The source row stride.
+ /// The destination row stride.
+ /// The tile X coordinate.
+ /// The tile Y coordinate.
+ private static void Transpose4x4(ReadOnlySpan source, Span destination, int sourceStride, int destinationStride, int x, int y)
+ {
+ ref byte sourceBase = ref MemoryMarshal.GetReference(source);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ Vector128 row0 = LoadFourBytes(ref sourceBase, ((y + 0) * sourceStride) + x);
+ Vector128 row1 = LoadFourBytes(ref sourceBase, ((y + 1) * sourceStride) + x);
+ Vector128 row2 = LoadFourBytes(ref sourceBase, ((y + 2) * sourceStride) + x);
+ Vector128 row3 = LoadFourBytes(ref sourceBase, ((y + 3) * sourceStride) + x);
+ Vector128 pair0 = Vector128_.UnpackLow(row0, row1);
+ Vector128 pair1 = Vector128_.UnpackLow(row2, row3);
+ Vector128 columns = Vector128_.UnpackLow(pair0.AsInt16(), pair1.AsInt16());
+
+ Vector128 packedColumns = columns.AsUInt32();
+ StoreFourBytes(packedColumns.GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y);
+ StoreFourBytes(packedColumns.GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y);
+ StoreFourBytes(packedColumns.GetElement(2), ref destinationBase, ((x + 2) * destinationStride) + y);
+ StoreFourBytes(packedColumns.GetElement(3), ref destinationBase, ((x + 3) * destinationStride) + y);
+ }
+
+ ///
+ /// Transposes one eight-by-eight tile of high-bit-depth prediction samples.
+ ///
+ /// The contiguous source block.
+ /// The destination block origin.
+ /// The source row stride.
+ /// The destination row stride.
+ /// The tile X coordinate.
+ /// The tile Y coordinate.
+ private static void Transpose8x8(ReadOnlySpan source, Span destination, int sourceStride, int destinationStride, int x, int y)
+ {
+ ref short sourceBase = ref MemoryMarshal.GetReference(source);
+ ref short destinationBase = ref MemoryMarshal.GetReference(destination);
+ Vector128 row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x));
+ Vector128 row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x));
+ Vector128 row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x));
+ Vector128 row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x));
+ Vector128 row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x));
+ Vector128 row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x));
+ Vector128 row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x));
+ Vector128 row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x));
+ Vector128 pair0 = Vector128_.UnpackLow(row0, row1);
+ Vector128 pair1 = Vector128_.UnpackHigh(row0, row1);
+ Vector128