diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 7f898dc5a..2c5450e0e 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -65,6 +65,8 @@ Immediate checkpoint: **complete layered AV1 image-item decoding through the exi
- [x] Store the eight fixed reference-validity, order-hint, and map-index tables inline on the frame header, retaining complete multi-bit order hints without per-header array allocations.
- [ ] Preserve reconstruction, reference-frame, primary-CDF, segmentation, loop-filter, and motion state across every dependent layer in one image-item decoder session.
- [ ] Implement the complete inter-frame entropy, mode, motion-vector, compound-prediction, inter-prediction, and warped/global-motion paths permitted by the image profile.
+ - [x] Implement allocation-free SIMD-first translational single-reference interpolation for regular, smooth, sharp, and bilinear filters across 8/10/12-bit samples. The predictor mirrors JPEG's closed static operator architecture, descends through `Vector512`, `Vector256`, and `Vector128` before scalar fallback, and passes the exact independent convolution oracle through `FeatureTestRunner`.
+ - [ ] Connect reference selection, motion-vector derivation, compound modes, warped/global motion, and reconstructed-plane writes through tile decoding, then verify them with independently encoded inter-frame AV1 image-layer fixtures.
- [ ] Return the explicitly selected spatial layer or the final displayed layer, keeping reference reconstruction separate from display-only film grain.
- [ ] Verify color and auxiliary-alpha output exactly against both pinned libavif progressive fixtures under normal SIMD dispatch and all required `FeatureTestRunner` fallbacks.
- [ ] Correct the audited 12-bit inverse ADST4, Identity4, and Identity16 SIMD arithmetic by widening only the libaom-widened multiply/accumulate operations, with exact conformant-range vectors and `FeatureTestRunner` coverage.
@@ -521,6 +523,10 @@ Implement and verify in dependency order:
- [x] Intra-block copy for still-image intra frames.
- [x] Decode tile-adaptive integer displacement vectors, derive and validate spatial references, apply the inter transform sets, and reconstruct luma and subsampled chroma through allocation-free `Vector512`/`Vector256`/`Vector128` operators with exact-width stores and scalar fallback. `FeatureTestRunner` verifies every transform width, bit-depth storage path, chroma phase, intrinsic tier, scalar continuation, and destination-padding boundary.
- [x] Verify displacement-vector entropy, spatial candidate ordering, wavefront legality, native 8/10/12-bit reconstruction, and presented output against independently encoded opaque intra-block-copy AVIF fixtures from the pinned generic libaom/libavif reference. The fixtures require actual intra-block-copy block selection, compare every native plane sample from retained scalar-decoder Y4M output, and compare every presented RGBA byte exactly under normal hardware dispatch and each narrower fallback configuration without a tolerance.
+- [ ] Inter-frame prediction for layered still-image items.
+ - [x] Implement allocation-free SIMD-first translational single-reference interpolation for regular, smooth, sharp, and bilinear filters; reduced four-sample kernels; horizontal, vertical, and separable two-dimensional convolution; exact AV1 rounding; 8/10/12-bit clipping; padded reference origins; and guarded destination strides. The operator contract and concrete operator files mirror JPEG color conversion, and `FeatureTestRunner` verifies normal, AVX-512-disabled, AVX-disabled, and scalar execution against an independent fixed-point oracle.
+ - [ ] Decode and connect reference indices, motion vectors, compound prediction, inter-intra prediction, masked blending, warped motion, global motion, and OBMC through reconstructed reference planes.
+ - [ ] Verify every connected inter mode and filter with independently encoded dependent-layer AV1 image-item fixtures and exact native-plane comparisons.
- [ ] Lossless and high-bit-depth reconstruction with correct clipping and intermediate precision.
- [x] Route lossless 4x4 blocks through allocation-free reversible inverse Walsh-Hadamard reconstruction for 8/10/12-bit samples, including the DC-only specialization, `Vector128` production traversal, scalar fallback, exact clipping, and `FeatureTestRunner` parity.
- [x] Verify lossless syntax, inverse quantization, prediction, and presented reconstruction with independently encoded 8/10/12-bit AVIF fixtures. The tests require coded residuals with palette and intra-block copy disabled, compare every native YUV sample with the pinned generic libaom-backed decoder, and compare every presented RGBA byte with pinned generic libavif exactly under normal hardware dispatch and the scalar fallback.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Arithmetic.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Arithmetic.cs
new file mode 100644
index 000000000..81d83db1e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Arithmetic.cs
@@ -0,0 +1,347 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Provides lane-wise convolution, rounding, clipping, and packing shared by every interpolation filter.
+///
+internal static partial class Av1InterPredictor
+{
+ ///
+ /// Convolves sixteen adjacent 8-bit samples into four signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref byte source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector128 initial,
+ out Vector128 result0,
+ out Vector128 result1,
+ out Vector128 result2,
+ out Vector128 result3)
+ {
+ result0 = initial;
+ result1 = initial;
+ result2 = initial;
+ result3 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector128 samples = Vector128.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector128 samples0, out Vector128 samples1, out Vector128 samples2, out Vector128 samples3);
+ Vector128 coefficient = Vector128.Create((int)Unsafe.Add(ref coefficients, tap));
+
+ // Each widened vector retains four consecutive source columns. Applying the same tap coefficient to all
+ // lanes evaluates sixteen independent finite-impulse-response filters without a horizontal reduction.
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ result2 += samples2 * coefficient;
+ result3 += samples3 * coefficient;
+ }
+ }
+
+ ///
+ /// Convolves thirty-two adjacent 8-bit samples into four signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref byte source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector256 initial,
+ out Vector256 result0,
+ out Vector256 result1,
+ out Vector256 result2,
+ out Vector256 result3)
+ {
+ result0 = initial;
+ result1 = initial;
+ result2 = initial;
+ result3 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector256 samples = Vector256.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector256 samples0, out Vector256 samples1, out Vector256 samples2, out Vector256 samples3);
+ Vector256 coefficient = Vector256.Create((int)Unsafe.Add(ref coefficients, tap));
+
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ result2 += samples2 * coefficient;
+ result3 += samples3 * coefficient;
+ }
+ }
+
+ ///
+ /// Convolves sixty-four adjacent 8-bit samples into four signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref byte source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector512 initial,
+ out Vector512 result0,
+ out Vector512 result1,
+ out Vector512 result2,
+ out Vector512 result3)
+ {
+ result0 = initial;
+ result1 = initial;
+ result2 = initial;
+ result3 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector512 samples = Vector512.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector512 samples0, out Vector512 samples1, out Vector512 samples2, out Vector512 samples3);
+ Vector512 coefficient = Vector512.Create((int)Unsafe.Add(ref coefficients, tap));
+
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ result2 += samples2 * coefficient;
+ result3 += samples3 * coefficient;
+ }
+ }
+
+ ///
+ /// Convolves eight adjacent nonnegative 16-bit samples into two signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref short source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector128 initial,
+ out Vector128 result0,
+ out Vector128 result1)
+ {
+ result0 = initial;
+ result1 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector128 samples = Vector128.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector128 samples0, out Vector128 samples1);
+ Vector128 coefficient = Vector128.Create((int)Unsafe.Add(ref coefficients, tap));
+
+ // Reconstructed 10- and 12-bit samples and biased 2D intermediates are below short.MaxValue, so signed
+ // widening preserves their values while allowing negative interpolation coefficients.
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ }
+ }
+
+ ///
+ /// Convolves sixteen adjacent nonnegative 16-bit samples into two signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref short source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector256 initial,
+ out Vector256 result0,
+ out Vector256 result1)
+ {
+ result0 = initial;
+ result1 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector256 samples = Vector256.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector256 samples0, out Vector256 samples1);
+ Vector256 coefficient = Vector256.Create((int)Unsafe.Add(ref coefficients, tap));
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ }
+ }
+
+ ///
+ /// Convolves thirty-two adjacent nonnegative 16-bit samples into two signed 32-bit accumulator vectors.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Convolve(
+ ref short source,
+ int tapStride,
+ nuint column,
+ ref short coefficients,
+ int tapCount,
+ Vector512 initial,
+ out Vector512 result0,
+ out Vector512 result1)
+ {
+ result0 = initial;
+ result1 = initial;
+
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ Vector512 samples = Vector512.LoadUnsafe(ref Unsafe.Add(ref source, tap * tapStride), column);
+ Av1IntraPredictorBase.Widen(samples, out Vector512 samples0, out Vector512 samples1);
+ Vector512 coefficient = Vector512.Create((int)Unsafe.Add(ref coefficients, tap));
+ result0 += samples0 * coefficient;
+ result1 += samples1 * coefficient;
+ }
+ }
+
+ ///
+ /// Applies AV1 power-of-two rounding to four-lane signed accumulators.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 RoundPowerOfTwo(Vector128 value, int bits)
+ => bits == 0 ? value : (value + Vector128.Create(1 << (bits - 1))) >> bits;
+
+ ///
+ /// Applies AV1 power-of-two rounding to eight-lane signed accumulators.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 RoundPowerOfTwo(Vector256 value, int bits)
+ => bits == 0 ? value : (value + Vector256.Create(1 << (bits - 1))) >> bits;
+
+ ///
+ /// Applies AV1 power-of-two rounding to sixteen-lane signed accumulators.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 RoundPowerOfTwo(Vector512 value, int bits)
+ => bits == 0 ? value : (value + Vector512.Create(1 << (bits - 1))) >> bits;
+
+ ///
+ /// Clips and packs sixteen signed accumulators into 8-bit samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 PackBytes(Vector128 result0, Vector128 result1, Vector128 result2, Vector128 result3)
+ {
+ Vector128 maximum = Vector128.Create((int)byte.MaxValue);
+ result0 = Vector128.Clamp(result0, Vector128.Zero, maximum);
+ result1 = Vector128.Clamp(result1, Vector128.Zero, maximum);
+ result2 = Vector128.Clamp(result2, Vector128.Zero, maximum);
+ result3 = Vector128.Clamp(result3, Vector128.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1, result2, result3);
+ }
+
+ ///
+ /// Clips and packs thirty-two signed accumulators into 8-bit samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 PackBytes(Vector256 result0, Vector256 result1, Vector256 result2, Vector256 result3)
+ {
+ Vector256 maximum = Vector256.Create((int)byte.MaxValue);
+ result0 = Vector256.Clamp(result0, Vector256.Zero, maximum);
+ result1 = Vector256.Clamp(result1, Vector256.Zero, maximum);
+ result2 = Vector256.Clamp(result2, Vector256.Zero, maximum);
+ result3 = Vector256.Clamp(result3, Vector256.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1, result2, result3);
+ }
+
+ ///
+ /// Clips and packs sixty-four signed accumulators into 8-bit samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 PackBytes(Vector512 result0, Vector512 result1, Vector512 result2, Vector512 result3)
+ {
+ Vector512 maximum = Vector512.Create((int)byte.MaxValue);
+ result0 = Vector512.Clamp(result0, Vector512.Zero, maximum);
+ result1 = Vector512.Clamp(result1, Vector512.Zero, maximum);
+ result2 = Vector512.Clamp(result2, Vector512.Zero, maximum);
+ result3 = Vector512.Clamp(result3, Vector512.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1, result2, result3);
+ }
+
+ ///
+ /// Clips and packs eight signed accumulators into high-bit-depth samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector128 PackHighBitDepth(Vector128 result0, Vector128 result1, int maximumValue)
+ {
+ Vector128 maximum = Vector128.Create(maximumValue);
+ result0 = Vector128.Clamp(result0, Vector128.Zero, maximum);
+ result1 = Vector128.Clamp(result1, Vector128.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1).AsUInt16();
+ }
+
+ ///
+ /// Clips and packs sixteen signed accumulators into high-bit-depth samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector256 PackHighBitDepth(Vector256 result0, Vector256 result1, int maximumValue)
+ {
+ Vector256 maximum = Vector256.Create(maximumValue);
+ result0 = Vector256.Clamp(result0, Vector256.Zero, maximum);
+ result1 = Vector256.Clamp(result1, Vector256.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1).AsUInt16();
+ }
+
+ ///
+ /// Clips and packs thirty-two signed accumulators into high-bit-depth samples.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Vector512 PackHighBitDepth(Vector512 result0, Vector512 result1, int maximumValue)
+ {
+ Vector512 maximum = Vector512.Create(maximumValue);
+ result0 = Vector512.Clamp(result0, Vector512.Zero, maximum);
+ result1 = Vector512.Clamp(result1, Vector512.Zero, maximum);
+ return Av1IntraPredictorBase.Narrow(result0, result1).AsUInt16();
+ }
+
+ ///
+ /// Computes one signed Q7 convolution sum from 8-bit samples.
+ ///
+ private static int ConvolveScalar(ref byte source, int sourceStride, ref short coefficients, int tapCount)
+ {
+ int sum = 0;
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ sum += Unsafe.Add(ref coefficients, tap) * Unsafe.Add(ref source, tap * sourceStride);
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Computes one signed Q7 convolution sum from high-bit-depth samples.
+ ///
+ private static int ConvolveScalar(ref ushort source, int sourceStride, ref short coefficients, int tapCount)
+ {
+ int sum = 0;
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ sum += Unsafe.Add(ref coefficients, tap) * Unsafe.Add(ref source, tap * sourceStride);
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Computes one signed Q7 convolution sum from biased intermediate samples.
+ ///
+ private static int ConvolveScalar(ref short source, int sourceStride, ref short coefficients, int tapCount)
+ {
+ int sum = 0;
+ for (int tap = 0; tap < tapCount; tap++)
+ {
+ sum += Unsafe.Add(ref coefficients, tap) * Unsafe.Add(ref source, tap * sourceStride);
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Rounds an integer after division by a power of two using AV1's unsigned-bias rule.
+ ///
+ private static int RoundPowerOfTwo(int value, int bits) => bits == 0 ? value : (value + (1 << (bits - 1))) >> bits;
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.BilinearOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.BilinearOperator.cs
new file mode 100644
index 000000000..5d81a3a37
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.BilinearOperator.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Defines bilinear interpolation for translational inter prediction.
+///
+internal static partial class Av1InterPredictor
+{
+ ///
+ /// Selects bilinear interpolation coefficients.
+ ///
+ internal readonly struct BilinearOperator : IAv1InterPredictorOperator
+ {
+ ///
+ public static ReadOnlySpan GetCoefficients(int phase, bool useReducedFilter)
+ => GetPhase(Bilinear, phase);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Dispatch.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Dispatch.cs
new file mode 100644
index 000000000..1076323ad
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Dispatch.cs
@@ -0,0 +1,887 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Selects interpolation filters and the widest supported traversal for a translational prediction block.
+///
+internal static partial class Av1InterPredictor
+{
+ ///
+ /// Selects an 8-bit horizontal interpolation operator.
+ ///
+ private static void Dispatch(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ Av1InterpolationFilter horizontalFilter,
+ Av1InterpolationFilter verticalFilter,
+ int horizontalPhase,
+ int verticalPhase,
+ Span scratch,
+ bool scalarOnly)
+ {
+ switch (horizontalFilter)
+ {
+ case Av1InterpolationFilter.Regular:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ scratch,
+ scalarOnly);
+
+ break;
+ case Av1InterpolationFilter.Smooth:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ scratch,
+ scalarOnly);
+
+ break;
+ case Av1InterpolationFilter.Sharp:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ scratch,
+ scalarOnly);
+
+ break;
+ default:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ scratch,
+ scalarOnly);
+
+ break;
+ }
+ }
+
+ ///
+ /// Selects a high-bit-depth horizontal interpolation operator.
+ ///
+ private static void Dispatch(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ Av1InterpolationFilter horizontalFilter,
+ Av1InterpolationFilter verticalFilter,
+ int horizontalPhase,
+ int verticalPhase,
+ int bitDepth,
+ Span scratch,
+ bool scalarOnly)
+ {
+ switch (horizontalFilter)
+ {
+ case Av1InterpolationFilter.Regular:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ bitDepth,
+ scratch,
+ scalarOnly);
+
+ break;
+ case Av1InterpolationFilter.Smooth:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ bitDepth,
+ scratch,
+ scalarOnly);
+
+ break;
+ case Av1InterpolationFilter.Sharp:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ bitDepth,
+ scratch,
+ scalarOnly);
+
+ break;
+ default:
+ DispatchVertical(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ verticalFilter,
+ horizontalPhase,
+ verticalPhase,
+ bitDepth,
+ scratch,
+ scalarOnly);
+
+ break;
+ }
+ }
+
+ ///
+ /// Copies an 8-bit integer-position block using the widest vector that fits a complete row prefix.
+ ///
+ private static void Copy(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ bool scalarOnly)
+ {
+ if (scalarOnly)
+ {
+ CopyScalar(source, sourceStride, sourceOrigin, destination, destinationStride, width, height);
+ return;
+ }
+
+ ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int column = 0;
+
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int column = 0;
+
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+
+ if (width < Vector128.Count)
+ {
+ StorePartial(Vector128.LoadUnsafe(ref sourceRow), ref destinationRow, width);
+ continue;
+ }
+
+ int vectorEnd = width - Vector128.Count;
+ int column = 0;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ CopyScalar(source, sourceStride, sourceOrigin, destination, destinationStride, width, height);
+ }
+
+ ///
+ /// Copies a high-bit-depth integer-position block using the widest vector that fits a complete row prefix.
+ ///
+ private static void Copy(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ bool scalarOnly)
+ {
+ if (scalarOnly)
+ {
+ CopyScalar(source, sourceStride, sourceOrigin, destination, destinationStride, width, height);
+ return;
+ }
+
+ ref ushort sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref ushort destinationBase = ref MemoryMarshal.GetReference(destination);
+
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ int vectorEnd = width - Vector512.Count;
+ for (int row = 0; row < height; row++)
+ {
+ ref ushort sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int column = 0;
+
+ for (; column <= vectorEnd; column += Vector512.Count)
+ {
+ Vector512.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ int vectorEnd = width - Vector256.Count;
+ for (int row = 0; row < height; row++)
+ {
+ ref ushort sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int column = 0;
+
+ for (; column <= vectorEnd; column += Vector256.Count)
+ {
+ Vector256.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ for (int row = 0; row < height; row++)
+ {
+ ref ushort sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+
+ if (width < Vector128.Count)
+ {
+ // Four high-bit-depth samples occupy exactly the lower 64 bits of the vector.
+ Vector128.LoadUnsafe(ref sourceRow).GetLower().StoreUnsafe(ref destinationRow);
+ continue;
+ }
+
+ int vectorEnd = width - Vector128.Count;
+ int column = 0;
+ for (; column <= vectorEnd; column += Vector128.Count)
+ {
+ Vector128.LoadUnsafe(ref sourceRow, (nuint)column).StoreUnsafe(ref destinationRow, (nuint)column);
+ }
+
+ for (; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+
+ return;
+ }
+
+ CopyScalar(source, sourceStride, sourceOrigin, destination, destinationStride, width, height);
+ }
+
+ ///
+ /// Applies a one-dimensional 8-bit filter using one SIMD width for the complete block.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ bool scalarOnly)
+ {
+ if (!scalarOnly)
+ {
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ Vector512.Zero);
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ Vector256.Zero);
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ Vector128.Zero);
+
+ return;
+ }
+ }
+
+ FilterDirectScalar(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound);
+ }
+
+ ///
+ /// Applies a one-dimensional high-bit-depth filter using one SIMD width for the complete block.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ int bitDepth,
+ bool scalarOnly)
+ {
+ if (!scalarOnly)
+ {
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ bitDepth,
+ Vector512.Zero);
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ bitDepth,
+ Vector256.Zero);
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ FilterDirect(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ bitDepth,
+ Vector128.Zero);
+
+ return;
+ }
+ }
+
+ FilterDirectScalar(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ coefficients,
+ tapCount,
+ sourceOffset,
+ tapStride,
+ firstRound,
+ secondRound,
+ bitDepth);
+ }
+
+ ///
+ /// Applies separable two-dimensional filtering to an 8-bit block using one SIMD width.
+ ///
+ private static void Filter2D(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan horizontalCoefficients,
+ int horizontalTapCount,
+ int horizontalSourceOffset,
+ ReadOnlySpan verticalCoefficients,
+ int verticalTapCount,
+ int verticalSourceOffset,
+ int bitDepth,
+ Span scratch,
+ bool scalarOnly)
+ {
+ if (!scalarOnly)
+ {
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ Round0Bits,
+ scratch,
+ Vector512.Zero);
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ Round0Bits,
+ scratch,
+ Vector256.Zero);
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ Round0Bits,
+ scratch,
+ Vector128.Zero);
+
+ return;
+ }
+ }
+
+ Filter2DScalar(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ Round0Bits,
+ scratch);
+ }
+
+ ///
+ /// Applies separable two-dimensional filtering to a high-bit-depth block using one SIMD width.
+ ///
+ private static void Filter2D(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan horizontalCoefficients,
+ int horizontalTapCount,
+ int horizontalSourceOffset,
+ ReadOnlySpan verticalCoefficients,
+ int verticalTapCount,
+ int verticalSourceOffset,
+ int bitDepth,
+ int round0,
+ Span scratch,
+ bool scalarOnly)
+ {
+ if (!scalarOnly)
+ {
+ if (Vector512.IsHardwareAccelerated && Vector.Count == Vector512.Count && width >= Vector512.Count)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ round0,
+ scratch,
+ Vector512.Zero);
+
+ return;
+ }
+
+ if (Vector256.IsHardwareAccelerated && width >= Vector256.Count)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ round0,
+ scratch,
+ Vector256.Zero);
+
+ return;
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ Filter2D(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ round0,
+ scratch,
+ Vector128.Zero);
+
+ return;
+ }
+ }
+
+ Filter2DScalar(
+ source,
+ sourceStride,
+ sourceOrigin,
+ destination,
+ destinationStride,
+ width,
+ height,
+ horizontalCoefficients,
+ horizontalTapCount,
+ horizontalSourceOffset,
+ verticalCoefficients,
+ verticalTapCount,
+ verticalSourceOffset,
+ bitDepth,
+ round0,
+ scratch);
+ }
+
+ ///
+ /// Copies an 8-bit integer-position prediction without explicit hardware intrinsics.
+ ///
+ private static void CopyScalar(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height)
+ {
+ ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+
+ for (int column = 0; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+ }
+
+ ///
+ /// Copies a high-bit-depth integer-position prediction without explicit hardware intrinsics.
+ ///
+ private static void CopyScalar(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height)
+ {
+ ref ushort sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref ushort destinationBase = ref MemoryMarshal.GetReference(destination);
+
+ for (int row = 0; row < height; row++)
+ {
+ ref ushort sourceRow = ref Unsafe.Add(ref sourceBase, row * sourceStride);
+ ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+
+ for (int column = 0; column < width; column++)
+ {
+ Unsafe.Add(ref destinationRow, column) = Unsafe.Add(ref sourceRow, column);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Filters.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Filters.cs
new file mode 100644
index 000000000..eba9293c5
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.Filters.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter;
+
+///
+/// Provides the normative Q7 interpolation coefficients used by AV1 inter prediction.
+///
+internal static partial class Av1InterPredictor
+{
+ ///
+ /// The number of stored coefficient positions in every decoder interpolation kernel.
+ ///
+ private const int FilterCoefficientCount = 8;
+
+ ///
+ /// Gets the regular eight-tap kernels for the sixteen subpixel phases.
+ ///
+ private static ReadOnlySpan RegularEightTap =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 2, -6, 126, 8, -2, 0, 0,
+ 0, 2, -10, 122, 18, -4, 0, 0,
+ 0, 2, -12, 116, 28, -8, 2, 0,
+ 0, 2, -14, 110, 38, -10, 2, 0,
+ 0, 2, -14, 102, 48, -12, 2, 0,
+ 0, 2, -16, 94, 58, -12, 2, 0,
+ 0, 2, -14, 84, 66, -12, 2, 0,
+ 0, 2, -14, 76, 76, -14, 2, 0,
+ 0, 2, -12, 66, 84, -14, 2, 0,
+ 0, 2, -12, 58, 94, -16, 2, 0,
+ 0, 2, -12, 48, 102, -14, 2, 0,
+ 0, 2, -10, 38, 110, -14, 2, 0,
+ 0, 2, -8, 28, 116, -12, 2, 0,
+ 0, 0, -4, 18, 122, -10, 2, 0,
+ 0, 0, -2, 8, 126, -6, 2, 0,
+ ];
+
+ ///
+ /// Gets the smooth eight-tap kernels for the sixteen subpixel phases.
+ ///
+ private static ReadOnlySpan SmoothEightTap =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 2, 28, 62, 34, 2, 0, 0,
+ 0, 0, 26, 62, 36, 4, 0, 0,
+ 0, 0, 22, 62, 40, 4, 0, 0,
+ 0, 0, 20, 60, 42, 6, 0, 0,
+ 0, 0, 18, 58, 44, 8, 0, 0,
+ 0, 0, 16, 56, 46, 10, 0, 0,
+ 0, -2, 16, 54, 48, 12, 0, 0,
+ 0, -2, 14, 52, 52, 14, -2, 0,
+ 0, 0, 12, 48, 54, 16, -2, 0,
+ 0, 0, 10, 46, 56, 16, 0, 0,
+ 0, 0, 8, 44, 58, 18, 0, 0,
+ 0, 0, 6, 42, 60, 20, 0, 0,
+ 0, 0, 4, 40, 62, 22, 0, 0,
+ 0, 0, 4, 36, 62, 26, 0, 0,
+ 0, 0, 2, 34, 62, 28, 2, 0,
+ ];
+
+ ///
+ /// Gets the sharp eight-tap kernels for the sixteen subpixel phases.
+ ///
+ private static ReadOnlySpan SharpEightTap =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ -2, 2, -6, 126, 8, -2, 2, 0,
+ -2, 6, -12, 124, 16, -6, 4, -2,
+ -2, 8, -18, 120, 26, -10, 6, -2,
+ -4, 10, -22, 116, 38, -14, 6, -2,
+ -4, 10, -22, 108, 48, -18, 8, -2,
+ -4, 10, -24, 100, 60, -20, 8, -2,
+ -4, 10, -24, 90, 70, -22, 10, -2,
+ -4, 12, -24, 80, 80, -24, 12, -4,
+ -2, 10, -22, 70, 90, -24, 10, -4,
+ -2, 8, -20, 60, 100, -24, 10, -4,
+ -2, 8, -18, 48, 108, -22, 10, -4,
+ -2, 6, -14, 38, 116, -22, 10, -4,
+ -2, 6, -10, 26, 120, -18, 8, -2,
+ -2, 4, -6, 16, 124, -12, 6, -2,
+ 0, 2, -2, 8, 126, -6, 2, -2,
+ ];
+
+ ///
+ /// Gets the regular reduced kernels selected when a block dimension is at most four samples.
+ ///
+ private static ReadOnlySpan RegularFourTap =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 0, -4, 126, 8, -2, 0, 0,
+ 0, 0, -8, 122, 18, -4, 0, 0,
+ 0, 0, -10, 116, 28, -6, 0, 0,
+ 0, 0, -12, 110, 38, -8, 0, 0,
+ 0, 0, -12, 102, 48, -10, 0, 0,
+ 0, 0, -14, 94, 58, -10, 0, 0,
+ 0, 0, -12, 84, 66, -10, 0, 0,
+ 0, 0, -12, 76, 76, -12, 0, 0,
+ 0, 0, -10, 66, 84, -12, 0, 0,
+ 0, 0, -10, 58, 94, -14, 0, 0,
+ 0, 0, -10, 48, 102, -12, 0, 0,
+ 0, 0, -8, 38, 110, -12, 0, 0,
+ 0, 0, -6, 28, 116, -10, 0, 0,
+ 0, 0, -4, 18, 122, -8, 0, 0,
+ 0, 0, -2, 8, 126, -4, 0, 0,
+ ];
+
+ ///
+ /// Gets the smooth reduced kernels selected when a block dimension is at most four samples.
+ ///
+ private static ReadOnlySpan SmoothFourTap =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 0, 30, 62, 34, 2, 0, 0,
+ 0, 0, 26, 62, 36, 4, 0, 0,
+ 0, 0, 22, 62, 40, 4, 0, 0,
+ 0, 0, 20, 60, 42, 6, 0, 0,
+ 0, 0, 18, 58, 44, 8, 0, 0,
+ 0, 0, 16, 56, 46, 10, 0, 0,
+ 0, 0, 14, 54, 48, 12, 0, 0,
+ 0, 0, 12, 52, 52, 12, 0, 0,
+ 0, 0, 12, 48, 54, 14, 0, 0,
+ 0, 0, 10, 46, 56, 16, 0, 0,
+ 0, 0, 8, 44, 58, 18, 0, 0,
+ 0, 0, 6, 42, 60, 20, 0, 0,
+ 0, 0, 4, 40, 62, 22, 0, 0,
+ 0, 0, 4, 36, 62, 26, 0, 0,
+ 0, 0, 2, 34, 62, 30, 0, 0,
+ ];
+
+ ///
+ /// Gets the bilinear kernels for the sixteen subpixel phases.
+ ///
+ private static ReadOnlySpan Bilinear =>
+ [
+ 0, 0, 0, 128, 0, 0, 0, 0,
+ 0, 0, 0, 120, 8, 0, 0, 0,
+ 0, 0, 0, 112, 16, 0, 0, 0,
+ 0, 0, 0, 104, 24, 0, 0, 0,
+ 0, 0, 0, 96, 32, 0, 0, 0,
+ 0, 0, 0, 88, 40, 0, 0, 0,
+ 0, 0, 0, 80, 48, 0, 0, 0,
+ 0, 0, 0, 72, 56, 0, 0, 0,
+ 0, 0, 0, 64, 64, 0, 0, 0,
+ 0, 0, 0, 56, 72, 0, 0, 0,
+ 0, 0, 0, 48, 80, 0, 0, 0,
+ 0, 0, 0, 40, 88, 0, 0, 0,
+ 0, 0, 0, 32, 96, 0, 0, 0,
+ 0, 0, 0, 24, 104, 0, 0, 0,
+ 0, 0, 0, 16, 112, 0, 0, 0,
+ 0, 0, 0, 8, 120, 0, 0, 0,
+ ];
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.OneDimension.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.OneDimension.cs
new file mode 100644
index 000000000..9bad7069c
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Inter/Av1InterPredictor.OneDimension.cs
@@ -0,0 +1,541 @@
+// 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.Inter;
+
+///
+/// Provides SIMD kernels for horizontal-only and vertical-only single-reference filtering.
+///
+internal static partial class Av1InterPredictor
+{
+ ///
+ /// Filters an 8-bit block in sixteen-sample vectors.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ Vector128 initial)
+ {
+ ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients);
+
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int processedColumns = 0;
+
+ if (width < Vector128.Count)
+ {
+ // Four- and eight-sample AV1 blocks are smaller than one byte vector. Reference-plane padding makes
+ // the complete load readable, while the width-specific store leaves adjacent destination samples intact.
+ Convolve(
+ ref sourceRow,
+ tapStride,
+ 0,
+ ref coefficientBase,
+ tapCount,
+ initial,
+ out Vector128 result0,
+ out Vector128 result1,
+ out Vector128 result2,
+ out Vector128 result3);
+
+ Round(ref result0, ref result1, ref result2, ref result3, firstRound, secondRound);
+ StorePartial(PackBytes(result0, result1, result2, result3), ref destinationRow, width);
+ continue;
+ }
+
+ int vectorEnd = width - Vector128.Count;
+ for (; processedColumns <= vectorEnd; processedColumns += Vector128.Count)
+ {
+ Convolve(
+ ref sourceRow,
+ tapStride,
+ (nuint)processedColumns,
+ ref coefficientBase,
+ tapCount,
+ initial,
+ out Vector128 result0,
+ out Vector128 result1,
+ out Vector128 result2,
+ out Vector128 result3);
+
+ Round(ref result0, ref result1, ref result2, ref result3, firstRound, secondRound);
+ PackBytes(result0, result1, result2, result3).StoreUnsafe(ref destinationRow, (nuint)processedColumns);
+ }
+
+ FilterDirectTail(ref sourceRow, ref destinationRow, processedColumns, width, tapStride, ref coefficientBase, tapCount, firstRound, secondRound);
+ }
+ }
+
+ ///
+ /// Filters an 8-bit block in thirty-two-sample vectors.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ Vector256 initial)
+ {
+ ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients);
+ int vectorEnd = width - Vector256.Count;
+
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int processedColumns = 0;
+
+ for (; processedColumns <= vectorEnd; processedColumns += Vector256.Count)
+ {
+ Convolve(
+ ref sourceRow,
+ tapStride,
+ (nuint)processedColumns,
+ ref coefficientBase,
+ tapCount,
+ initial,
+ out Vector256 result0,
+ out Vector256 result1,
+ out Vector256 result2,
+ out Vector256 result3);
+
+ Round(ref result0, ref result1, ref result2, ref result3, firstRound, secondRound);
+ PackBytes(result0, result1, result2, result3).StoreUnsafe(ref destinationRow, (nuint)processedColumns);
+ }
+
+ FilterDirectTail(ref sourceRow, ref destinationRow, processedColumns, width, tapStride, ref coefficientBase, tapCount, firstRound, secondRound);
+ }
+ }
+
+ ///
+ /// Filters an 8-bit block in sixty-four-sample vectors.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ Vector512 initial)
+ {
+ ref byte sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients);
+ int vectorEnd = width - Vector512.Count;
+
+ for (int row = 0; row < height; row++)
+ {
+ ref byte sourceRow = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset);
+ ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int processedColumns = 0;
+
+ for (; processedColumns <= vectorEnd; processedColumns += Vector512.Count)
+ {
+ Convolve(
+ ref sourceRow,
+ tapStride,
+ (nuint)processedColumns,
+ ref coefficientBase,
+ tapCount,
+ initial,
+ out Vector512 result0,
+ out Vector512 result1,
+ out Vector512 result2,
+ out Vector512 result3);
+
+ Round(ref result0, ref result1, ref result2, ref result3, firstRound, secondRound);
+ PackBytes(result0, result1, result2, result3).StoreUnsafe(ref destinationRow, (nuint)processedColumns);
+ }
+
+ FilterDirectTail(ref sourceRow, ref destinationRow, processedColumns, width, tapStride, ref coefficientBase, tapCount, firstRound, secondRound);
+ }
+ }
+
+ ///
+ /// Filters a high-bit-depth block in eight-sample vectors.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ int bitDepth,
+ Vector128 initial)
+ {
+ ref ushort sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref ushort destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients);
+ int maximum = (1 << bitDepth) - 1;
+
+ for (int row = 0; row < height; row++)
+ {
+ ref ushort sourceRowUnsigned = ref Unsafe.Add(ref sourceBase, (row * sourceStride) + sourceOffset);
+ ref short sourceRow = ref Unsafe.As(ref sourceRowUnsigned);
+ ref ushort destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
+ int processedColumns = 0;
+
+ if (width < Vector128.Count)
+ {
+ Convolve(ref sourceRow, tapStride, 0, ref coefficientBase, tapCount, initial, out Vector128 result0, out Vector128 result1);
+ Round(ref result0, ref result1, firstRound, secondRound);
+
+ // The only legal AV1 width below eight is four samples, exactly the lower Vector64 half.
+ PackHighBitDepth(result0, result1, maximum).GetLower().StoreUnsafe(ref destinationRow);
+ continue;
+ }
+
+ int vectorEnd = width - Vector128.Count;
+ for (; processedColumns <= vectorEnd; processedColumns += Vector128.Count)
+ {
+ Convolve(ref sourceRow, tapStride, (nuint)processedColumns, ref coefficientBase, tapCount, initial, out Vector128 result0, out Vector128 result1);
+ Round(ref result0, ref result1, firstRound, secondRound);
+ PackHighBitDepth(result0, result1, maximum).StoreUnsafe(ref destinationRow, (nuint)processedColumns);
+ }
+
+ FilterDirectTail(ref sourceRowUnsigned, ref destinationRow, processedColumns, width, tapStride, ref coefficientBase, tapCount, firstRound, secondRound, maximum);
+ }
+ }
+
+ ///
+ /// Filters a high-bit-depth block in sixteen-sample vectors.
+ ///
+ private static void FilterDirect(
+ ReadOnlySpan source,
+ int sourceStride,
+ int sourceOrigin,
+ Span destination,
+ int destinationStride,
+ int width,
+ int height,
+ ReadOnlySpan coefficients,
+ int tapCount,
+ int sourceOffset,
+ int tapStride,
+ int firstRound,
+ int secondRound,
+ int bitDepth,
+ Vector256 initial)
+ {
+ ref ushort sourceBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(source), sourceOrigin);
+ ref ushort destinationBase = ref MemoryMarshal.GetReference(destination);
+ ref short coefficientBase = ref MemoryMarshal.GetReference(coefficients);
+ int maximum = (1 << bitDepth) - 1;
+ int vectorEnd = width - Vector256