Browse Source

Rewrite AV1 intra prediction for SIMD

pull/2633/head
James Jackson-South 2 months ago
parent
commit
12189d6ec5
  1. 69
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
  2. 245
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcIntraPredictor.cs
  3. 77
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
  4. 86
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
  5. 77
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
  6. 619
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Simd.cs
  7. 313
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.Transpose.cs
  8. 443
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.cs
  9. 118
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
  10. 124
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
  11. 119
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
  12. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.DcOperator.cs
  13. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.Directional157Operator.cs
  14. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.HorizontalOperator.cs
  15. 596
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.Operator.cs
  16. 25
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.OperatorContract.cs
  17. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.PaethOperator.cs
  18. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.VerticalOperator.cs
  19. 191
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.cs
  20. 121
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictorBase.cs
  21. 602
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HighBitDepthPredictor.cs
  22. 72
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
  23. 54
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.HorizontalOperator.cs
  24. 344
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.Operator.cs
  25. 180
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.OperatorContract.cs
  26. 166
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.Packing.cs
  27. 209
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.PaethOperator.cs
  28. 161
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothHorizontalOperator.cs
  29. 241
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothOperator.cs
  30. 164
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothVerticalOperator.cs
  31. 54
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.VerticalOperator.cs
  32. 129
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictorBase.cs
  33. 99
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
  34. 596
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
  35. 275
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  36. 89
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
  37. 135
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
  38. 87
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
  39. 72
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
  40. 19
      src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs
  41. 18
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs
  42. 1210
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

69
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs

@ -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;
/// <summary>
/// Predicts an 8-bit AV1 block with the sample-domain midpoint when neither top nor left neighbors are available.
/// </summary>
internal readonly struct Av1DcFillPredictor : IAv1Predictor
{
/// <summary>
/// The number of samples written to each destination row.
/// </summary>
private readonly uint blockWidth;
/// <summary>
/// The number of destination rows.
/// </summary>
private readonly uint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcFillPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1DcFillPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcFillPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1DcFillPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block with the 8-bit midpoint value.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcFillPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> 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);
}
}
}

245
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;
/// <summary>
/// Reconstructs AV1 DC intra-prediction blocks from the available neighboring samples.
/// </summary>
internal static class Av1DcIntraPredictor
{
/// <summary>
/// Predicts an 8-bit DC block.
/// </summary>
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public static void Predict(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> 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);
}
}
/// <summary>
/// Predicts a high-bit-depth DC block.
/// </summary>
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
/// <param name="bitDepth">The reconstructed sample precision.</param>
public static void Predict(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> 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);
}
}
/// <summary>
/// Predicts an 8-bit DC block without hardware intrinsics.
/// </summary>
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> 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;
}
}
}
/// <summary>
/// Predicts a high-bit-depth DC block without hardware intrinsics.
/// </summary>
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
/// <param name="bitDepth">The reconstructed sample precision.</param>
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> 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;
}
}
}
/// <summary>
/// Sums 8-bit neighboring samples using the widest available SIMD width.
/// </summary>
/// <param name="samples">The samples to sum.</param>
/// <returns>The exact sum.</returns>
private static int Sum(ReadOnlySpan<byte> samples)
{
ref byte samplesBase = ref MemoryMarshal.GetReference(samples);
int sum = 0;
int index = 0;
if (Vector512.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector512<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count)
{
(Vector512<ushort> low, Vector512<ushort> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index));
sum += Vector512.Sum(low) + Vector512.Sum(high);
}
}
if (Vector256.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector256<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count)
{
(Vector256<ushort> low, Vector256<ushort> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index));
sum += Vector256.Sum(low) + Vector256.Sum(high);
}
}
if (Vector128.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector128<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count)
{
(Vector128<ushort> low, Vector128<ushort> 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;
}
/// <summary>
/// Sums high-bit-depth neighboring samples using the widest available SIMD width.
/// </summary>
/// <param name="samples">The samples to sum.</param>
/// <returns>The exact sum.</returns>
private static int Sum(ReadOnlySpan<short> samples)
{
ref short samplesBase = ref MemoryMarshal.GetReference(samples);
int sum = 0;
int index = 0;
if (Vector512.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector512<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count)
{
(Vector512<int> low, Vector512<int> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index));
sum += Vector512.Sum(low) + Vector512.Sum(high);
}
}
if (Vector256.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector256<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count)
{
(Vector256<int> low, Vector256<int> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index));
sum += Vector256.Sum(low) + Vector256.Sum(high);
}
}
if (Vector128.IsHardwareAccelerated)
{
int oneVectorFromEnd = samples.Length - Vector128<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count)
{
(Vector128<int> low, Vector128<int> 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;
}
/// <summary>
/// Sums 8-bit neighboring samples without hardware intrinsics.
/// </summary>
/// <param name="samples">The samples to sum.</param>
/// <returns>The exact sum.</returns>
private static int SumScalar(ReadOnlySpan<byte> samples)
{
int sum = 0;
foreach (byte sample in samples)
{
sum += sample;
}
return sum;
}
/// <summary>
/// Sums high-bit-depth neighboring samples without hardware intrinsics.
/// </summary>
/// <param name="samples">The samples to sum.</param>
/// <returns>The exact sum.</returns>
private static int SumScalar(ReadOnlySpan<short> samples)
{
int sum = 0;
foreach (short sample in samples)
{
sum += sample;
}
return sum;
}
}

77
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs

@ -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;
/// <summary>
/// Predicts an 8-bit AV1 block from the rounded average of its available left neighboring samples.
/// </summary>
internal readonly struct Av1DcLeftPredictor : IAv1Predictor
{
/// <summary>
/// The number of samples written to each destination row.
/// </summary>
private readonly uint blockWidth;
/// <summary>
/// The number of left samples averaged and destination rows written.
/// </summary>
private readonly uint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcLeftPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1DcLeftPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcLeftPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1DcLeftPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block from its left neighboring samples.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcLeftPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> 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);
}
}
}

86
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs

@ -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;
/// <summary>
/// Predicts an 8-bit AV1 block from the rounded average of its top and left neighboring samples.
/// </summary>
internal readonly struct Av1DcPredictor : IAv1Predictor
{
/// <summary>
/// The number of top samples averaged and samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of left samples averaged and destination rows written.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1DcPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1DcPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block from its top and left neighboring samples.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> 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);
}
}
}

77
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs

@ -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;
/// <summary>
/// Predicts an 8-bit AV1 block from the rounded average of its available top neighboring samples.
/// </summary>
internal readonly struct Av1DcTopPredictor : IAv1Predictor
{
/// <summary>
/// The number of top samples averaged and samples written to each destination row.
/// </summary>
private readonly uint blockWidth;
/// <summary>
/// The number of destination rows.
/// </summary>
private readonly uint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcTopPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1DcTopPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DcTopPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1DcTopPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block from its top neighboring samples.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples.</param>
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1DcTopPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> 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);
}
}
}

619
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;
/// <content>
/// Provides the SIMD projection kernels for AV1 directional intra prediction.
/// </content>
internal static partial class Av1DirectionalIntraPredictor
{
/// <summary>
/// Gets the indices of even bytes in one upsampled reference vector.
/// </summary>
private static Vector128<byte> EvenByteIndices => Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14);
/// <summary>
/// Gets the indices of odd bytes in one upsampled reference vector.
/// </summary>
private static Vector128<byte> OddByteIndices => Vector128.Create((byte)1, 3, 5, 7, 9, 11, 13, 15, 1, 3, 5, 7, 9, 11, 13, 15);
/// <summary>
/// Gets the indices of even high-bit-depth samples in one upsampled reference vector.
/// </summary>
private static Vector128<short> EvenShortIndices => Vector128.Create((short)0, 2, 4, 6, 0, 2, 4, 6);
/// <summary>
/// Gets the indices of odd high-bit-depth samples in one upsampled reference vector.
/// </summary>
private static Vector128<short> OddShortIndices => Vector128.Create((short)1, 3, 5, 7, 1, 3, 5, 7);
/// <summary>
/// Predicts one 8-bit zone 1 block using contiguous SIMD projection rows.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone1(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> 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);
}
}
/// <summary>
/// Predicts one high-bit-depth zone 1 block using contiguous SIMD projection rows.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone1(Span<short> destination, int destinationStride, ReadOnlySpan<short> 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);
}
}
/// <summary>
/// Predicts one 8-bit zone 2 block using vectorized left gathers and contiguous top projection rows.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
/// <param name="dx">The horizontal Q8 derivative.</param>
/// <param name="dy">The vertical Q8 derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone2(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> 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<byte> 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);
}
}
}
/// <summary>
/// Predicts one high-bit-depth zone 2 block using vectorized left gathers and contiguous top projection rows.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
/// <param name="dx">The horizontal Q8 derivative.</param>
/// <param name="dy">The vertical Q8 derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone2(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> 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<short> 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);
}
}
}
/// <summary>
/// Interpolates one 8-bit projection row.
/// </summary>
/// <param name="destination">The destination row.</param>
/// <param name="reference">The projected reference samples.</param>
/// <param name="basis">The first integral reference coordinate.</param>
/// <param name="weight">The right-sample interpolation weight.</param>
/// <param name="upsample">Whether consecutive output samples advance two reference positions.</param>
/// <param name="maximumBasis">The final extended reference coordinate, or <see cref="int.MaxValue"/> when the row cannot reach it.</param>
private static void InterpolateRow(Span<byte> destination, ReadOnlySpan<byte> 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<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count)
{
Vector512<byte> left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector512<byte> 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<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count)
{
Vector256<byte> left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector256<byte> 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<byte>.Count;
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count)
{
Vector128<byte> left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector128<byte> 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<byte> 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<byte> left = Vector128.ShuffleNative(source, EvenByteIndices);
Vector128<byte> right = Vector128.ShuffleNative(source, OddByteIndices);
Vector128<byte> prediction = Interpolate(left, right, weight);
Unsafe.As<byte, ulong>(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<int> 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<int> 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));
}
}
/// <summary>
/// Interpolates one high-bit-depth projection row.
/// </summary>
/// <param name="destination">The destination row.</param>
/// <param name="reference">The projected reference samples.</param>
/// <param name="basis">The first integral reference coordinate.</param>
/// <param name="weight">The right-sample interpolation weight.</param>
/// <param name="upsample">Whether consecutive output samples advance two reference positions.</param>
/// <param name="maximumBasis">The final extended reference coordinate, or <see cref="int.MaxValue"/> when the row cannot reach it.</param>
private static void InterpolateRow(Span<short> destination, ReadOnlySpan<short> 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<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count)
{
Vector512<short> left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector512<short> 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<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count)
{
Vector256<short> left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector256<short> 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<short>.Count;
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count)
{
Vector128<short> left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index));
Vector128<short> 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<short> source = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + (index * 2)));
Vector128<short> left = Vector128.ShuffleNative(source, EvenShortIndices);
Vector128<short> right = Vector128.ShuffleNative(source, OddShortIndices);
Vector128<short> prediction = Interpolate(left, right, weight);
Unsafe.As<short, ulong>(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<int> 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<int> 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));
}
}
/// <summary>
/// Interpolates the left-edge prefix of one 8-bit zone 2 row.
/// </summary>
/// <param name="destination">The destination prefix.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="projection">The first Q6 left projection.</param>
/// <param name="derivative">The Q8 derivative subtracted between columns.</param>
/// <param name="upsample">Whether the left reference contains half-sample positions.</param>
private static void InterpolateLeft(Span<byte> destination, ReadOnlySpan<byte> 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<int> 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<int> 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<int> 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);
}
}
/// <summary>
/// Interpolates the left-edge prefix of one high-bit-depth zone 2 row.
/// </summary>
/// <param name="destination">The destination prefix.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="projection">The first Q6 left projection.</param>
/// <param name="derivative">The Q8 derivative subtracted between columns.</param>
/// <param name="upsample">Whether the left reference contains half-sample positions.</param>
private static void InterpolateLeft(Span<short> destination, ReadOnlySpan<short> 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<int> 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<int> 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<int> 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);
}
}
/// <summary>
/// Interpolates sixty-four pairs of 8-bit references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<byte> Interpolate(Vector512<byte> left, Vector512<byte> right, int weight)
{
(Vector512<ushort> leftLow, Vector512<ushort> leftHigh) = Vector512.Widen(left);
(Vector512<ushort> rightLow, Vector512<ushort> rightHigh) = Vector512.Widen(right);
Vector512<ushort> rounding = Vector512.Create((ushort)16);
Vector512<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
Vector512<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
return Vector512.Narrow(low, high);
}
/// <summary>
/// Interpolates thirty-two pairs of 8-bit references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> Interpolate(Vector256<byte> left, Vector256<byte> right, int weight)
{
(Vector256<ushort> leftLow, Vector256<ushort> leftHigh) = Vector256.Widen(left);
(Vector256<ushort> rightLow, Vector256<ushort> rightHigh) = Vector256.Widen(right);
Vector256<ushort> rounding = Vector256.Create((ushort)16);
Vector256<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
Vector256<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
return Vector256.Narrow(low, high);
}
/// <summary>
/// Interpolates sixteen pairs of 8-bit references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> Interpolate(Vector128<byte> left, Vector128<byte> right, int weight)
{
(Vector128<ushort> leftLow, Vector128<ushort> leftHigh) = Vector128.Widen(left);
(Vector128<ushort> rightLow, Vector128<ushort> rightHigh) = Vector128.Widen(right);
Vector128<ushort> rounding = Vector128.Create((ushort)16);
Vector128<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5;
Vector128<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5;
return Vector128.Narrow(low, high);
}
/// <summary>
/// Interpolates thirty-two pairs of high-bit-depth references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<short> Interpolate(Vector512<short> left, Vector512<short> right, int weight)
{
(Vector512<int> leftLow, Vector512<int> leftHigh) = Vector512.Widen(left);
(Vector512<int> rightLow, Vector512<int> rightHigh) = Vector512.Widen(right);
Vector512<int> rounding = Vector512.Create(16);
Vector512<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
Vector512<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
return Vector512.Narrow(low, high);
}
/// <summary>
/// Interpolates sixteen pairs of high-bit-depth references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<short> Interpolate(Vector256<short> left, Vector256<short> right, int weight)
{
(Vector256<int> leftLow, Vector256<int> leftHigh) = Vector256.Widen(left);
(Vector256<int> rightLow, Vector256<int> rightHigh) = Vector256.Widen(right);
Vector256<int> rounding = Vector256.Create(16);
Vector256<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
Vector256<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
return Vector256.Narrow(low, high);
}
/// <summary>
/// Interpolates eight pairs of high-bit-depth references.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weight">The right-sample weight.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<short> Interpolate(Vector128<short> left, Vector128<short> right, int weight)
{
(Vector128<int> leftLow, Vector128<int> leftHigh) = Vector128.Widen(left);
(Vector128<int> rightLow, Vector128<int> rightHigh) = Vector128.Widen(right);
Vector128<int> rounding = Vector128.Create(16);
Vector128<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5;
Vector128<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5;
return Vector128.Narrow(low, high);
}
/// <summary>
/// Interpolates four widened reference pairs with independent weights.
/// </summary>
/// <param name="left">The left interpolation samples.</param>
/// <param name="right">The right interpolation samples.</param>
/// <param name="weights">The right-sample weights.</param>
/// <returns>The rounded interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Interpolate(Vector128<int> left, Vector128<int> right, Vector128<int> weights)
=> ((left * (Vector128.Create(32) - weights)) + (right * weights) + Vector128.Create(16)) >> 5;
/// <summary>
/// Stores four widened predictions as packed 8-bit samples.
/// </summary>
/// <param name="prediction">The widened predictions.</param>
/// <param name="destination">The first destination sample.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourBytes(Vector128<int> prediction, ref byte destination)
{
Vector128<ushort> narrowed16 = Vector128.Narrow(prediction.AsUInt32(), Vector128<uint>.Zero);
Vector128<byte> narrowed8 = Vector128.Narrow(narrowed16, Vector128<ushort>.Zero);
Unsafe.As<byte, uint>(ref destination) = narrowed8.AsUInt32().GetElement(0);
}
/// <summary>
/// Stores four widened predictions as packed high-bit-depth samples.
/// </summary>
/// <param name="prediction">The widened predictions.</param>
/// <param name="destination">The first destination sample.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourShorts(Vector128<int> prediction, ref short destination)
{
Vector128<short> narrowed = Vector128.Narrow(prediction, Vector128<int>.Zero);
Unsafe.As<short, ulong>(ref destination) = narrowed.AsUInt64().GetElement(0);
}
}

313
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;
/// <content>
/// Provides SIMD block transposition for zone 3 directional prediction.
/// </content>
internal static partial class Av1DirectionalIntraPredictor
{
/// <summary>
/// Transposes a contiguous 8-bit prediction block into the reconstructed destination.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceWidth">The source row width.</param>
/// <param name="sourceHeight">The number of source rows.</param>
/// <param name="destinationStride">The destination row stride.</param>
private static void Transpose(ReadOnlySpan<byte> source, Span<byte> 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];
}
}
}
/// <summary>
/// Transposes a contiguous high-bit-depth prediction block into the reconstructed destination.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceWidth">The source row width.</param>
/// <param name="sourceHeight">The number of source rows.</param>
/// <param name="destinationStride">The destination row stride.</param>
private static void Transpose(ReadOnlySpan<short> source, Span<short> 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];
}
}
}
/// <summary>
/// Transposes one eight-by-eight tile of 8-bit prediction samples.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceStride">The source row stride.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="x">The tile X coordinate.</param>
/// <param name="y">The tile Y coordinate.</param>
private static void Transpose8x8(ReadOnlySpan<byte> source, Span<byte> destination, int sourceStride, int destinationStride, int x, int y)
{
ref byte sourceBase = ref MemoryMarshal.GetReference(source);
ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
Vector128<byte> row0 = LoadEightBytes(ref sourceBase, ((y + 0) * sourceStride) + x);
Vector128<byte> row1 = LoadEightBytes(ref sourceBase, ((y + 1) * sourceStride) + x);
Vector128<byte> row2 = LoadEightBytes(ref sourceBase, ((y + 2) * sourceStride) + x);
Vector128<byte> row3 = LoadEightBytes(ref sourceBase, ((y + 3) * sourceStride) + x);
Vector128<byte> row4 = LoadEightBytes(ref sourceBase, ((y + 4) * sourceStride) + x);
Vector128<byte> row5 = LoadEightBytes(ref sourceBase, ((y + 5) * sourceStride) + x);
Vector128<byte> row6 = LoadEightBytes(ref sourceBase, ((y + 6) * sourceStride) + x);
Vector128<byte> 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<byte> pair0 = Vector128_.UnpackLow(row0, row1);
Vector128<byte> pair2 = Vector128_.UnpackLow(row2, row3);
Vector128<byte> pair4 = Vector128_.UnpackLow(row4, row5);
Vector128<byte> pair6 = Vector128_.UnpackLow(row6, row7);
Vector128<short> quad0 = Vector128_.UnpackLow(pair0.AsInt16(), pair2.AsInt16());
Vector128<short> quad1 = Vector128_.UnpackHigh(pair0.AsInt16(), pair2.AsInt16());
Vector128<short> quad4 = Vector128_.UnpackLow(pair4.AsInt16(), pair6.AsInt16());
Vector128<short> quad5 = Vector128_.UnpackHigh(pair4.AsInt16(), pair6.AsInt16());
Vector128<int> columns01 = Vector128_.UnpackLow(quad0.AsInt32(), quad4.AsInt32());
Vector128<int> columns23 = Vector128_.UnpackHigh(quad0.AsInt32(), quad4.AsInt32());
Vector128<int> columns45 = Vector128_.UnpackLow(quad1.AsInt32(), quad5.AsInt32());
Vector128<int> 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);
}
/// <summary>
/// Transposes one four-by-four tile of 8-bit prediction samples.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceStride">The source row stride.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="x">The tile X coordinate.</param>
/// <param name="y">The tile Y coordinate.</param>
private static void Transpose4x4(ReadOnlySpan<byte> source, Span<byte> destination, int sourceStride, int destinationStride, int x, int y)
{
ref byte sourceBase = ref MemoryMarshal.GetReference(source);
ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
Vector128<byte> row0 = LoadFourBytes(ref sourceBase, ((y + 0) * sourceStride) + x);
Vector128<byte> row1 = LoadFourBytes(ref sourceBase, ((y + 1) * sourceStride) + x);
Vector128<byte> row2 = LoadFourBytes(ref sourceBase, ((y + 2) * sourceStride) + x);
Vector128<byte> row3 = LoadFourBytes(ref sourceBase, ((y + 3) * sourceStride) + x);
Vector128<byte> pair0 = Vector128_.UnpackLow(row0, row1);
Vector128<byte> pair1 = Vector128_.UnpackLow(row2, row3);
Vector128<short> columns = Vector128_.UnpackLow(pair0.AsInt16(), pair1.AsInt16());
Vector128<uint> 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);
}
/// <summary>
/// Transposes one eight-by-eight tile of high-bit-depth prediction samples.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceStride">The source row stride.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="x">The tile X coordinate.</param>
/// <param name="y">The tile Y coordinate.</param>
private static void Transpose8x8(ReadOnlySpan<short> source, Span<short> destination, int sourceStride, int destinationStride, int x, int y)
{
ref short sourceBase = ref MemoryMarshal.GetReference(source);
ref short destinationBase = ref MemoryMarshal.GetReference(destination);
Vector128<short> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x));
Vector128<short> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x));
Vector128<short> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x));
Vector128<short> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x));
Vector128<short> row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x));
Vector128<short> row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x));
Vector128<short> row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x));
Vector128<short> row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x));
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1);
Vector128<short> pair1 = Vector128_.UnpackHigh(row0, row1);
Vector128<short> pair2 = Vector128_.UnpackLow(row2, row3);
Vector128<short> pair3 = Vector128_.UnpackHigh(row2, row3);
Vector128<short> pair4 = Vector128_.UnpackLow(row4, row5);
Vector128<short> pair5 = Vector128_.UnpackHigh(row4, row5);
Vector128<short> pair6 = Vector128_.UnpackLow(row6, row7);
Vector128<short> pair7 = Vector128_.UnpackHigh(row6, row7);
Vector128<int> quad0 = Vector128_.UnpackLow(pair0.AsInt32(), pair2.AsInt32());
Vector128<int> quad1 = Vector128_.UnpackHigh(pair0.AsInt32(), pair2.AsInt32());
Vector128<int> quad2 = Vector128_.UnpackLow(pair1.AsInt32(), pair3.AsInt32());
Vector128<int> quad3 = Vector128_.UnpackHigh(pair1.AsInt32(), pair3.AsInt32());
Vector128<int> quad4 = Vector128_.UnpackLow(pair4.AsInt32(), pair6.AsInt32());
Vector128<int> quad5 = Vector128_.UnpackHigh(pair4.AsInt32(), pair6.AsInt32());
Vector128<int> quad6 = Vector128_.UnpackLow(pair5.AsInt32(), pair7.AsInt32());
Vector128<int> quad7 = Vector128_.UnpackHigh(pair5.AsInt32(), pair7.AsInt32());
Vector128<short> column0 = Vector128_.UnpackLow(quad0.AsInt64(), quad4.AsInt64()).AsInt16();
Vector128<short> column1 = Vector128_.UnpackHigh(quad0.AsInt64(), quad4.AsInt64()).AsInt16();
Vector128<short> column2 = Vector128_.UnpackLow(quad1.AsInt64(), quad5.AsInt64()).AsInt16();
Vector128<short> column3 = Vector128_.UnpackHigh(quad1.AsInt64(), quad5.AsInt64()).AsInt16();
Vector128<short> column4 = Vector128_.UnpackLow(quad2.AsInt64(), quad6.AsInt64()).AsInt16();
Vector128<short> column5 = Vector128_.UnpackHigh(quad2.AsInt64(), quad6.AsInt64()).AsInt16();
Vector128<short> column6 = Vector128_.UnpackLow(quad3.AsInt64(), quad7.AsInt64()).AsInt16();
Vector128<short> column7 = Vector128_.UnpackHigh(quad3.AsInt64(), quad7.AsInt64()).AsInt16();
column0.StoreUnsafe(ref destinationBase, (nuint)(((x + 0) * destinationStride) + y));
column1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * destinationStride) + y));
column2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * destinationStride) + y));
column3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * destinationStride) + y));
column4.StoreUnsafe(ref destinationBase, (nuint)(((x + 4) * destinationStride) + y));
column5.StoreUnsafe(ref destinationBase, (nuint)(((x + 5) * destinationStride) + y));
column6.StoreUnsafe(ref destinationBase, (nuint)(((x + 6) * destinationStride) + y));
column7.StoreUnsafe(ref destinationBase, (nuint)(((x + 7) * destinationStride) + y));
}
/// <summary>
/// Transposes one four-by-four tile of high-bit-depth prediction samples.
/// </summary>
/// <param name="source">The contiguous source block.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="sourceStride">The source row stride.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="x">The tile X coordinate.</param>
/// <param name="y">The tile Y coordinate.</param>
private static void Transpose4x4(ReadOnlySpan<short> source, Span<short> destination, int sourceStride, int destinationStride, int x, int y)
{
ref short sourceBase = ref MemoryMarshal.GetReference(source);
ref short destinationBase = ref MemoryMarshal.GetReference(destination);
Vector128<short> row0 = LoadFourShorts(ref sourceBase, ((y + 0) * sourceStride) + x);
Vector128<short> row1 = LoadFourShorts(ref sourceBase, ((y + 1) * sourceStride) + x);
Vector128<short> row2 = LoadFourShorts(ref sourceBase, ((y + 2) * sourceStride) + x);
Vector128<short> row3 = LoadFourShorts(ref sourceBase, ((y + 3) * sourceStride) + x);
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1);
Vector128<short> pair1 = Vector128_.UnpackLow(row2, row3);
Vector128<int> columns01 = Vector128_.UnpackLow(pair0.AsInt32(), pair1.AsInt32());
Vector128<int> columns23 = Vector128_.UnpackHigh(pair0.AsInt32(), pair1.AsInt32());
StoreFourShorts(columns01.AsUInt64().GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y);
StoreFourShorts(columns01.AsUInt64().GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y);
StoreFourShorts(columns23.AsUInt64().GetElement(0), ref destinationBase, ((x + 2) * destinationStride) + y);
StoreFourShorts(columns23.AsUInt64().GetElement(1), ref destinationBase, ((x + 3) * destinationStride) + y);
}
/// <summary>
/// Loads eight bytes into the lower half of a vector without reading past a tile row.
/// </summary>
/// <param name="source">The source buffer origin.</param>
/// <param name="offset">The source offset.</param>
/// <returns>The loaded bytes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadEightBytes(ref byte source, int offset)
=> Vector128.Create(Unsafe.As<byte, ulong>(ref Unsafe.Add(ref source, offset)), 0UL).AsByte();
/// <summary>
/// Loads four bytes into the low vector lanes without reading past a tile row.
/// </summary>
/// <param name="source">The source buffer origin.</param>
/// <param name="offset">The source offset.</param>
/// <returns>The loaded bytes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadFourBytes(ref byte source, int offset)
=> Vector128.Create(Unsafe.As<byte, uint>(ref Unsafe.Add(ref source, offset)), 0U, 0U, 0U).AsByte();
/// <summary>
/// Loads four high-bit-depth samples into the lower half of a vector without reading past a tile row.
/// </summary>
/// <param name="source">The source buffer origin.</param>
/// <param name="offset">The source offset.</param>
/// <returns>The loaded samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<short> LoadFourShorts(ref short source, int offset)
=> Vector128.Create(Unsafe.As<short, ulong>(ref Unsafe.Add(ref source, offset)), 0UL).AsInt16();
/// <summary>
/// Stores the lower eight bytes of a vector.
/// </summary>
/// <param name="source">The packed bytes.</param>
/// <param name="destination">The destination buffer origin.</param>
/// <param name="offset">The destination offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreEightBytes(ulong source, ref byte destination, int offset)
=> Unsafe.As<byte, ulong>(ref Unsafe.Add(ref destination, offset)) = source;
/// <summary>
/// Stores four bytes from the low vector lanes.
/// </summary>
/// <param name="source">The packed bytes.</param>
/// <param name="destination">The destination buffer origin.</param>
/// <param name="offset">The destination offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourBytes(uint source, ref byte destination, int offset)
=> Unsafe.As<byte, uint>(ref Unsafe.Add(ref destination, offset)) = source;
/// <summary>
/// Stores four high-bit-depth samples from the low vector lanes.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="destination">The destination buffer origin.</param>
/// <param name="offset">The destination offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourShorts(ulong source, ref short destination, int offset)
=> Unsafe.As<short, ulong>(ref Unsafe.Add(ref destination, offset)) = source;
}

443
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalIntraPredictor.cs

@ -0,0 +1,443 @@
// 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;
/// <summary>
/// Reconstructs AV1 directional intra-prediction blocks from prepared neighboring samples.
/// </summary>
/// <remarks>
/// The three projection zones implement the directional prediction process in section 7.11.2.4 of the AV1 specification.
/// </remarks>
internal static partial class Av1DirectionalIntraPredictor
{
/// <summary>
/// The largest number of samples required to transpose a directional prediction block.
/// </summary>
public const int ScratchLength = 64 * 64;
/// <summary>
/// Gets the Q8 directional derivatives indexed by acute prediction angle.
/// </summary>
private static ReadOnlySpan<int> DirectionalIntraDerivative =>
[
// Zero entries represent angles which AV1 never signals. Direct indexing avoids a search or division in
// each directional block while retaining the exact fixed-point projections from the normative table.
0, 0, 0, 1023, 0, 0, 547, 0, 0, 372, 0, 0, 0, 0, 273, 0, 0, 215, 0, 0, 178, 0, 0,
151, 0, 0, 132, 0, 0, 116, 0, 0, 102, 0, 0, 0, 90, 0, 0, 80, 0, 0, 71, 0, 0, 64, 0, 0,
57, 0, 0, 51, 0, 0, 45, 0, 0, 0, 40, 0, 0, 35, 0, 0, 31, 0, 0, 27, 0, 0, 23, 0, 0,
19, 0, 0, 15, 0, 0, 0, 0, 11, 0, 0, 7, 0, 0, 3, 0, 0,
];
/// <summary>
/// Predicts an 8-bit directional block using the widest available SIMD path.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="above">The prepared top reference, including any required extension.</param>
/// <param name="left">The prepared left reference, including any required extension.</param>
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
/// <param name="angle">The adjusted prediction angle.</param>
/// <param name="scratch">The caller-owned block transposition workspace.</param>
public static void Predict(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<byte> scratch)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
if (angle is > 0 and < 90)
{
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height);
}
else if (angle is > 90 and < 180)
{
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height);
}
else if (angle is > 180 and < 270)
{
// libaom computes zone 3 as a zone 1 block with swapped dimensions, then transposes it. This preserves
// contiguous reference reads and destination stores in both hot stages instead of scattering columns.
Span<byte> transposed = scratch[..(width * height)];
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width);
Transpose(transposed, destination, height, width, destinationStride);
}
else
{
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal;
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height);
}
}
/// <summary>
/// Predicts a high-bit-depth directional block using the widest available SIMD path.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="above">The prepared top reference, including any required extension.</param>
/// <param name="left">The prepared left reference, including any required extension.</param>
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
/// <param name="angle">The adjusted prediction angle.</param>
/// <param name="scratch">The caller-owned block transposition workspace.</param>
public static void Predict(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<short> scratch)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
if (angle is > 0 and < 90)
{
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height);
}
else if (angle is > 90 and < 180)
{
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height);
}
else if (angle is > 180 and < 270)
{
Span<short> transposed = scratch[..(width * height)];
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width);
Transpose(transposed, destination, height, width, destinationStride);
}
else
{
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal;
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height);
}
}
/// <summary>
/// Predicts an 8-bit directional block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="above">The prepared top reference, including any required extension.</param>
/// <param name="left">The prepared left reference, including any required extension.</param>
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
/// <param name="angle">The adjusted prediction angle.</param>
public static void PredictScalar(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
if (angle is > 0 and < 90)
{
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height);
}
else if (angle is > 90 and < 180)
{
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height);
}
else if (angle is > 180 and < 270)
{
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height);
}
else
{
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal;
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height);
}
}
/// <summary>
/// Predicts a high-bit-depth directional block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="above">The prepared top reference, including any required extension.</param>
/// <param name="left">The prepared left reference, including any required extension.</param>
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
/// <param name="angle">The adjusted prediction angle.</param>
public static void PredictScalar(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
if (angle is > 0 and < 90)
{
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height);
}
else if (angle is > 90 and < 180)
{
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height);
}
else if (angle is > 180 and < 270)
{
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height);
}
else
{
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal;
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height);
}
}
/// <summary>
/// Gets the horizontal Q8 projection derivative for an adjusted angle.
/// </summary>
/// <param name="angle">The adjusted prediction angle.</param>
/// <returns>The horizontal derivative, or one when the selected zone does not consume it.</returns>
public static int GetDeltaX(int angle)
=> angle switch
{
> 0 and < 90 => DirectionalIntraDerivative[angle],
> 90 and < 180 => DirectionalIntraDerivative[180 - angle],
_ => 1,
};
/// <summary>
/// Gets the vertical Q8 projection derivative for an adjusted angle.
/// </summary>
/// <param name="angle">The adjusted prediction angle.</param>
/// <returns>The vertical derivative, or one when the selected zone does not consume it.</returns>
public static int GetDeltaY(int angle)
=> angle switch
{
> 90 and < 180 => DirectionalIntraDerivative[angle - 90],
> 180 and < 270 => DirectionalIntraDerivative[270 - angle],
_ => 1,
};
/// <summary>
/// Predicts one 8-bit zone 1 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone1Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> 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 basisIncrement = 1 << upsampleShift;
int projection = derivative;
ref byte aboveBase = ref Unsafe.AsRef(in above[0]);
for (int row = 0; row < height; row++, projection += derivative)
{
int basis = projection >> fractionBits;
int weight = ((projection << upsampleShift) & 0x3F) >> 1;
ref byte destinationRow = ref destination[row * destinationStride];
for (int column = 0; column < width; column++, basis += basisIncrement)
{
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis
? (byte)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5)
: Unsafe.Add(ref aboveBase, maximumBasis);
}
}
}
/// <summary>
/// Predicts one high-bit-depth zone 1 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone1Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> 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 basisIncrement = 1 << upsampleShift;
int projection = derivative;
ref short aboveBase = ref Unsafe.AsRef(in above[0]);
for (int row = 0; row < height; row++, projection += derivative)
{
int basis = projection >> fractionBits;
int weight = ((projection << upsampleShift) & 0x3F) >> 1;
ref short destinationRow = ref destination[row * destinationStride];
for (int column = 0; column < width; column++, basis += basisIncrement)
{
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis
? (short)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5)
: Unsafe.Add(ref aboveBase, maximumBasis);
}
}
}
/// <summary>
/// Predicts one 8-bit zone 2 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
/// <param name="dx">The horizontal Q8 derivative.</param>
/// <param name="dy">The vertical Q8 derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone2Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height)
{
int aboveShift = upsampleAbove ? 1 : 0;
int leftShift = upsampleLeft ? 1 : 0;
int minimumTopBasis = -(1 << aboveShift);
int topFractionBits = 6 - aboveShift;
int leftFractionBits = 6 - leftShift;
int topBasisIncrement = 1 << aboveShift;
int topProjection = -dx;
ref byte aboveBase = ref Unsafe.AsRef(in above[0]);
ref byte leftBase = ref Unsafe.AsRef(in left[0]);
for (int row = 0; row < height; row++, topProjection -= dx)
{
int topBasis = topProjection >> topFractionBits;
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1;
int leftProjection = (row << 6) - dy;
ref byte destinationRow = ref destination[row * destinationStride];
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy)
{
int prediction;
if (topBasis >= minimumTopBasis)
{
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight);
}
else
{
int leftBasis = leftProjection >> leftFractionBits;
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight);
}
Unsafe.Add(ref destinationRow, column) = (byte)((prediction + 16) >> 5);
}
}
}
/// <summary>
/// Predicts one high-bit-depth zone 2 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="above">The projected top reference.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
/// <param name="dx">The horizontal Q8 derivative.</param>
/// <param name="dy">The vertical Q8 derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone2Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height)
{
int aboveShift = upsampleAbove ? 1 : 0;
int leftShift = upsampleLeft ? 1 : 0;
int minimumTopBasis = -(1 << aboveShift);
int topFractionBits = 6 - aboveShift;
int leftFractionBits = 6 - leftShift;
int topBasisIncrement = 1 << aboveShift;
int topProjection = -dx;
ref short aboveBase = ref Unsafe.AsRef(in above[0]);
ref short leftBase = ref Unsafe.AsRef(in left[0]);
for (int row = 0; row < height; row++, topProjection -= dx)
{
int topBasis = topProjection >> topFractionBits;
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1;
int leftProjection = (row << 6) - dy;
ref short destinationRow = ref destination[row * destinationStride];
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy)
{
int prediction;
if (topBasis >= minimumTopBasis)
{
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight);
}
else
{
int leftBasis = leftProjection >> leftFractionBits;
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight);
}
Unsafe.Add(ref destinationRow, column) = (short)((prediction + 16) >> 5);
}
}
}
/// <summary>
/// Predicts one 8-bit zone 3 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone3Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> left, bool upsample, int derivative, int width, int height)
{
int upsampleShift = upsample ? 1 : 0;
int maximumBasis = (width + height - 1) << upsampleShift;
int fractionBits = 6 - upsampleShift;
int basisIncrement = 1 << upsampleShift;
int projection = derivative;
ref byte leftBase = ref Unsafe.AsRef(in left[0]);
for (int column = 0; column < width; column++, projection += derivative)
{
int basis = projection >> fractionBits;
int weight = ((projection << upsampleShift) & 0x3F) >> 1;
for (int row = 0; row < height; row++, basis += basisIncrement)
{
destination[(row * destinationStride) + column] = basis < maximumBasis
? (byte)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5)
: Unsafe.Add(ref leftBase, maximumBasis);
}
}
}
/// <summary>
/// Predicts one high-bit-depth zone 3 block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="left">The projected left reference.</param>
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
/// <param name="derivative">The Q8 projection derivative.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void PredictZone3Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> left, bool upsample, int derivative, int width, int height)
{
int upsampleShift = upsample ? 1 : 0;
int maximumBasis = (width + height - 1) << upsampleShift;
int fractionBits = 6 - upsampleShift;
int basisIncrement = 1 << upsampleShift;
int projection = derivative;
ref short leftBase = ref Unsafe.AsRef(in left[0]);
for (int column = 0; column < width; column++, projection += derivative)
{
int basis = projection >> fractionBits;
int weight = ((projection << upsampleShift) & 0x3F) >> 1;
for (int row = 0; row < height; row++, basis += basisIncrement)
{
destination[(row * destinationStride) + column] = basis < maximumBasis
? (short)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5)
: Unsafe.Add(ref leftBase, maximumBasis);
}
}
}
}

118
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs

@ -1,118 +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;
/// <summary>
/// Produces 8-bit AV1 directional intra predictions for angles in zone 1.
/// </summary>
/// <remarks>
/// Zone 1 projects the top reference samples into the block for prediction angles less than 90 degrees.
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
/// </remarks>
internal readonly struct Av1DirectionalZone1Predictor
{
/// <summary>
/// The width of the prediction block in samples.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The height of the prediction block in samples.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone1Predictor"/> struct for the specified block dimensions.
/// </summary>
/// <param name="blockSize">The dimensions of the prediction block.</param>
public Av1DirectionalZone1Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone1Predictor"/> struct for the specified transform size.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
public Av1DirectionalZone1Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Produces an 8-bit zone 1 directional prediction for a transform block.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, bool upsampleAbove, int dx)
=> new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx);
/// <summary>
/// Produces an 8-bit zone 1 directional prediction for this block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="upsample">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z1_c</c> in SVT-AV1.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, bool upsample, int dx)
{
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride));
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination));
int upsampleAbove = upsample ? 1 : 0;
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
int maxBasisX = (((int)this.blockWidth + (int)this.blockHeight) - 1) << upsampleAbove;
int fractionBitCount = 6 - upsampleAbove;
int basisIncrement = 1 << upsampleAbove;
int x = dx;
for (nuint r = 0; r < this.blockHeight; ++r)
{
// AV1 retains six fractional projection bits, or five after reference upsampling.
// The interpolation weights sum to 32 because the low projection bit is discarded.
int basis = x >> fractionBitCount, shift = ((x << upsampleAbove) & 0x3F) >> 1;
if (basis >= maxBasisX)
{
for (nuint i = r; i < this.blockHeight; ++i)
{
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref aboveRef, maxBasisX), (uint)this.blockWidth);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
return;
}
for (nuint c = 0; c < this.blockWidth; ++c)
{
if (basis < maxBasisX)
{
int val;
val = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift);
val = Av1Math.RoundPowerOf2(val, 5);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
}
else
{
Unsafe.Add(ref destinationRef, c) = Unsafe.Add(ref aboveRef, maxBasisX);
}
basis += basisIncrement;
}
x += dx;
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

124
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs

@ -1,124 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Produces 8-bit AV1 directional intra predictions for angles in zone 2.
/// </summary>
/// <remarks>
/// Zone 2 projects both top and left reference samples into the block for prediction angles between 90 and 180 degrees.
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
/// </remarks>
internal readonly struct Av1DirectionalZone2Predictor
{
/// <summary>
/// The width of the prediction block in samples.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The height of the prediction block in samples.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone2Predictor"/> struct for the specified block dimensions.
/// </summary>
/// <param name="blockSize">The dimensions of the prediction block.</param>
public Av1DirectionalZone2Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone2Predictor"/> struct for the specified transform size.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
public Av1DirectionalZone2Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Produces an 8-bit zone 2 directional prediction for a transform block.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy)
=> new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleLeft, dx, dy);
/// <summary>
/// Produces an 8-bit zone 2 directional prediction for this block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="doUpsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="doUpsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z2_c</c> in SVT-AV1.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy)
{
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));
int upsampleAbove = doUpsampleAbove ? 1 : 0;
int upsampleLeft = doUpsampleLeft ? 1 : 0;
ref byte aboveRef = ref above[0];
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
int minBasisX = -(1 << upsampleAbove);
int fractionBitCountX = 6 - upsampleAbove;
int fractionBitCountY = 6 - upsampleLeft;
int basisIncrementX = 1 << upsampleAbove;
int x = -dx;
for (nuint r = 0; r < this.blockHeight; ++r)
{
int val;
int base1 = x >> fractionBitCountX;
int y = ((int)r << 6) - dy;
for (nuint c = 0; c < this.blockWidth; ++c, base1 += basisIncrementX, y -= dy)
{
// A nonnegative top projection uses the above edge. Once the projection crosses
// the top-left corner, the same destination sample is projected from the left edge.
if (base1 >= minBasisX)
{
int shift1 = ((x * (1 << upsampleAbove)) & 0x3F) >> 1;
val = (Unsafe.Add(ref aboveRef, base1) * (32 - shift1)) + (Unsafe.Add(ref aboveRef, base1 + 1) * shift1);
val = Av1Math.RoundPowerOf2(val, 5);
}
else
{
int base2 = y >> fractionBitCountY;
Guard.MustBeGreaterThanOrEqualTo(base2, -(1 << upsampleLeft), nameof(base2));
int shift2 = ((y * (1 << upsampleLeft)) & 0x3F) >> 1;
val = (Unsafe.Add(ref leftRef, base2) * (32 - shift2)) + (Unsafe.Add(ref leftRef, base2 + 1) * shift2);
val = Av1Math.RoundPowerOf2(val, 5);
}
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255);
}
x -= dx;
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

119
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs

@ -1,119 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Produces 8-bit AV1 directional intra predictions for angles in zone 3.
/// </summary>
/// <remarks>
/// Zone 3 projects the left reference samples into the block for prediction angles greater than 180 degrees.
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
/// </remarks>
internal readonly struct Av1DirectionalZone3Predictor
{
/// <summary>
/// The width of the prediction block in samples.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The height of the prediction block in samples.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone3Predictor"/> struct for the specified block dimensions.
/// </summary>
/// <param name="blockSize">The dimensions of the prediction block.</param>
public Av1DirectionalZone3Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1DirectionalZone3Predictor"/> struct for the specified transform size.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
public Av1DirectionalZone3Predictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Produces an 8-bit zone 3 directional prediction for a transform block.
/// </summary>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> left, bool upsampleLeft, int dx, int dy)
=> new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleLeft, dx, dy);
/// <summary>
/// Produces an 8-bit zone 3 directional prediction for this block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="upsample">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z3_c</c> in SVT-AV1.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> left, bool upsample, int dx, int dy)
{
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));
int upsampleLeft = upsample ? 1 : 0;
ref byte leftRef = ref left[0];
ref byte destinationRef = ref destination[0];
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction.");
Guard.MustBeGreaterThan(dy, 0, nameof(dy));
int maxBasisY = ((int)this.blockWidth + (int)this.blockHeight - 1) << upsampleLeft;
int fractionBitCount = 6 - upsampleLeft;
int basisIncrement = 1 << upsampleLeft;
int y = dy;
for (nuint c = 0; c < this.blockWidth; ++c)
{
// Zone 3 is the transpose of zone 1: columns advance along the projected left edge,
// while rows advance through the reference samples for each destination column.
int basis = y >> fractionBitCount;
int shift = ((y << upsampleLeft) & 0x3F) >> 1;
for (nuint r = 0; r < this.blockHeight; ++r)
{
if (basis < maxBasisY)
{
int val;
val = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift);
val = Av1Math.RoundPowerOf2(val, 5);
Unsafe.Add(ref destinationRef, (r * stride) + c) = (byte)Av1Math.Clamp(val, 0, 255);
}
else
{
for (; r < this.blockHeight; ++r)
{
Unsafe.Add(ref destinationRef, (r * stride) + c) = left[maxBasisY];
}
break;
}
basis += basisIncrement;
}
y += dy;
}
}
}

31
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.DcOperator.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the AV1 DC filter-intra coefficient operator.
/// </summary>
internal readonly struct DcOperator : IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.DC;
/// <inheritdoc/>
public static ReadOnlySpan<sbyte> Taps =>
[
-6, 10, 0, 0, 0, 12, 0,
-5, 2, 10, 0, 0, 9, 0,
-3, 1, 1, 10, 0, 7, 0,
-3, 1, 1, 2, 10, 5, 0,
-4, 6, 0, 0, 0, 2, 12,
-3, 2, 6, 0, 0, 2, 9,
-3, 2, 2, 6, 0, 2, 7,
-3, 1, 2, 2, 6, 3, 5,
];
}
}

31
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.Directional157Operator.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the AV1 157-degree directional filter-intra coefficient operator.
/// </summary>
internal readonly struct Directional157Operator : IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Directional157;
/// <inheritdoc/>
public static ReadOnlySpan<sbyte> Taps =>
[
-2, 8, 0, 0, 0, 10, 0,
-1, 3, 8, 0, 0, 6, 0,
-1, 2, 3, 8, 0, 4, 0,
0, 1, 2, 3, 8, 2, 0,
-1, 4, 0, 0, 0, 3, 10,
-1, 3, 4, 0, 0, 4, 6,
-1, 2, 3, 4, 0, 4, 4,
-1, 2, 2, 3, 4, 3, 3,
];
}
}

31
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.HorizontalOperator.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the AV1 horizontal filter-intra coefficient operator.
/// </summary>
internal readonly struct HorizontalOperator : IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Horizontal;
/// <inheritdoc/>
public static ReadOnlySpan<sbyte> Taps =>
[
-8, 8, 0, 0, 0, 16, 0,
-8, 0, 8, 0, 0, 16, 0,
-8, 0, 0, 8, 0, 16, 0,
-8, 0, 0, 0, 8, 16, 0,
-4, 4, 0, 0, 0, 0, 16,
-4, 0, 4, 0, 0, 0, 16,
-4, 0, 0, 4, 0, 0, 16,
-4, 0, 0, 0, 4, 0, 16,
];
}
}

596
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.Operator.cs

@ -0,0 +1,596 @@
// 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.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Applies one closed filter-intra coefficient operator using the widest useful SIMD width.
/// </summary>
/// <typeparam name="TOperator">The filter-intra coefficient set.</typeparam>
internal sealed class Av1FilterIntraPredictor<TOperator> : Av1FilterIntraPredictorBase
where TOperator : struct, IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public override Av1FilterIntraMode Mode => TOperator.Mode;
/// <inheritdoc/>
public override void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch)
{
Span<byte> buffer = scratch[..ScratchLength];
ref byte bufferBase = ref MemoryMarshal.GetReference(buffer);
ref byte aboveBase = ref MemoryMarshal.GetReference(above);
ref byte leftBase = ref MemoryMarshal.GetReference(left);
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps);
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1));
if (Vector256.IsHardwareAccelerated)
{
Vector256<int> tap0 = CreateTapVector256(ref taps, 0);
Vector256<int> tap1 = CreateTapVector256(ref taps, 1);
Vector256<int> tap2 = CreateTapVector256(ref taps, 2);
Vector256<int> tap3 = CreateTapVector256(ref taps, 3);
Vector256<int> tap4 = CreateTapVector256(ref taps, 4);
Vector256<int> tap5 = CreateTapVector256(ref taps, 5);
Vector256<int> tap6 = CreateTapVector256(ref taps, 6);
Vector256<int> rounding = Vector256.Create(8);
Vector256<int> maximum = Vector256.Create(255);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
Vector256<int> prediction = Calculate(
tap0,
tap1,
tap2,
tap3,
tap4,
tap5,
tap6,
Unsafe.Add(ref bufferBase, sourceOffset),
Unsafe.Add(ref bufferBase, sourceOffset + 1),
Unsafe.Add(ref bufferBase, sourceOffset + 2),
Unsafe.Add(ref bufferBase, sourceOffset + 3),
Unsafe.Add(ref bufferBase, sourceOffset + 4),
Unsafe.Add(ref bufferBase, sourceOffset + BufferStride),
Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)));
prediction = Vector256.Clamp((prediction + rounding) >> 4, Vector256<int>.Zero, maximum);
StoreEightBytes(prediction, ref bufferBase, (row * BufferStride) + column, ((row + 1) * BufferStride) + column);
}
}
}
else if (Vector128.IsHardwareAccelerated)
{
Vector128<int> tap0Low = CreateTapVector128(ref taps, 0, 0);
Vector128<int> tap1Low = CreateTapVector128(ref taps, 1, 0);
Vector128<int> tap2Low = CreateTapVector128(ref taps, 2, 0);
Vector128<int> tap3Low = CreateTapVector128(ref taps, 3, 0);
Vector128<int> tap4Low = CreateTapVector128(ref taps, 4, 0);
Vector128<int> tap5Low = CreateTapVector128(ref taps, 5, 0);
Vector128<int> tap6Low = CreateTapVector128(ref taps, 6, 0);
Vector128<int> tap0High = CreateTapVector128(ref taps, 0, 4);
Vector128<int> tap1High = CreateTapVector128(ref taps, 1, 4);
Vector128<int> tap2High = CreateTapVector128(ref taps, 2, 4);
Vector128<int> tap3High = CreateTapVector128(ref taps, 3, 4);
Vector128<int> tap4High = CreateTapVector128(ref taps, 4, 4);
Vector128<int> tap5High = CreateTapVector128(ref taps, 5, 4);
Vector128<int> tap6High = CreateTapVector128(ref taps, 6, 4);
Vector128<int> rounding = Vector128.Create(8);
Vector128<int> maximum = Vector128.Create(255);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferBase, sourceOffset);
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride);
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride));
Vector128<int> low = Calculate(tap0Low, tap1Low, tap2Low, tap3Low, tap4Low, tap5Low, tap6Low, p0, p1, p2, p3, p4, p5, p6);
Vector128<int> high = Calculate(tap0High, tap1High, tap2High, tap3High, tap4High, tap5High, tap6High, p0, p1, p2, p3, p4, p5, p6);
low = Vector128.Clamp((low + rounding) >> 4, Vector128<int>.Zero, maximum);
high = Vector128.Clamp((high + rounding) >> 4, Vector128<int>.Zero, maximum);
StoreFourBytes(low, ref bufferBase, (row * BufferStride) + column);
StoreFourBytes(high, ref bufferBase, ((row + 1) * BufferStride) + column);
}
}
}
else
{
PredictGroupsScalar(buffer, width, height, 255, ref taps);
}
CopyToDestination(buffer, destination, destinationStride, width, height);
}
/// <inheritdoc/>
public override void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch)
{
Span<short> buffer = scratch[..ScratchLength];
ref short bufferBase = ref MemoryMarshal.GetReference(buffer);
ref short aboveBase = ref MemoryMarshal.GetReference(above);
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps);
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1));
int maximum = (1 << bitDepth) - 1;
if (Vector256.IsHardwareAccelerated)
{
Vector256<int> tap0 = CreateTapVector256(ref taps, 0);
Vector256<int> tap1 = CreateTapVector256(ref taps, 1);
Vector256<int> tap2 = CreateTapVector256(ref taps, 2);
Vector256<int> tap3 = CreateTapVector256(ref taps, 3);
Vector256<int> tap4 = CreateTapVector256(ref taps, 4);
Vector256<int> tap5 = CreateTapVector256(ref taps, 5);
Vector256<int> tap6 = CreateTapVector256(ref taps, 6);
Vector256<int> rounding = Vector256.Create(8);
Vector256<int> maximumVector = Vector256.Create(maximum);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
Vector256<int> prediction = Calculate(
tap0,
tap1,
tap2,
tap3,
tap4,
tap5,
tap6,
Unsafe.Add(ref bufferBase, sourceOffset),
Unsafe.Add(ref bufferBase, sourceOffset + 1),
Unsafe.Add(ref bufferBase, sourceOffset + 2),
Unsafe.Add(ref bufferBase, sourceOffset + 3),
Unsafe.Add(ref bufferBase, sourceOffset + 4),
Unsafe.Add(ref bufferBase, sourceOffset + BufferStride),
Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)));
prediction = Vector256.Clamp((prediction + rounding) >> 4, Vector256<int>.Zero, maximumVector);
StoreEightShorts(prediction, ref bufferBase, (row * BufferStride) + column, ((row + 1) * BufferStride) + column);
}
}
}
else if (Vector128.IsHardwareAccelerated)
{
Vector128<int> tap0Low = CreateTapVector128(ref taps, 0, 0);
Vector128<int> tap1Low = CreateTapVector128(ref taps, 1, 0);
Vector128<int> tap2Low = CreateTapVector128(ref taps, 2, 0);
Vector128<int> tap3Low = CreateTapVector128(ref taps, 3, 0);
Vector128<int> tap4Low = CreateTapVector128(ref taps, 4, 0);
Vector128<int> tap5Low = CreateTapVector128(ref taps, 5, 0);
Vector128<int> tap6Low = CreateTapVector128(ref taps, 6, 0);
Vector128<int> tap0High = CreateTapVector128(ref taps, 0, 4);
Vector128<int> tap1High = CreateTapVector128(ref taps, 1, 4);
Vector128<int> tap2High = CreateTapVector128(ref taps, 2, 4);
Vector128<int> tap3High = CreateTapVector128(ref taps, 3, 4);
Vector128<int> tap4High = CreateTapVector128(ref taps, 4, 4);
Vector128<int> tap5High = CreateTapVector128(ref taps, 5, 4);
Vector128<int> tap6High = CreateTapVector128(ref taps, 6, 4);
Vector128<int> rounding = Vector128.Create(8);
Vector128<int> maximumVector = Vector128.Create(maximum);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferBase, sourceOffset);
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride);
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride));
Vector128<int> low = Calculate(tap0Low, tap1Low, tap2Low, tap3Low, tap4Low, tap5Low, tap6Low, p0, p1, p2, p3, p4, p5, p6);
Vector128<int> high = Calculate(tap0High, tap1High, tap2High, tap3High, tap4High, tap5High, tap6High, p0, p1, p2, p3, p4, p5, p6);
low = Vector128.Clamp((low + rounding) >> 4, Vector128<int>.Zero, maximumVector);
high = Vector128.Clamp((high + rounding) >> 4, Vector128<int>.Zero, maximumVector);
StoreFourShorts(low, ref bufferBase, (row * BufferStride) + column);
StoreFourShorts(high, ref bufferBase, ((row + 1) * BufferStride) + column);
}
}
}
else
{
PredictGroupsScalar(buffer, width, height, maximum, ref taps);
}
CopyToDestination(buffer, destination, destinationStride, width, height);
}
/// <inheritdoc/>
public override void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch)
{
Span<byte> buffer = scratch[..ScratchLength];
ref byte aboveBase = ref MemoryMarshal.GetReference(above);
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps);
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1));
PredictGroupsScalar(buffer, width, height, 255, ref taps);
CopyToDestinationScalar(buffer, destination, destinationStride, width, height);
}
/// <inheritdoc/>
public override void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch)
{
Span<short> buffer = scratch[..ScratchLength];
ref short aboveBase = ref MemoryMarshal.GetReference(above);
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps);
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1));
PredictGroupsScalar(buffer, width, height, (1 << bitDepth) - 1, ref taps);
CopyToDestinationScalar(buffer, destination, destinationStride, width, height);
}
/// <summary>
/// Initializes an 8-bit recursive filter-intra workspace from the prepared top and left references.
/// </summary>
/// <param name="buffer">The recursive workspace.</param>
/// <param name="above">The top reference.</param>
/// <param name="left">The left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="topLeft">The shared top-left reference.</param>
private static void Initialize(Span<byte> buffer, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, byte topLeft)
{
buffer[0] = topLeft;
above[..width].CopyTo(buffer[1..]);
for (int row = 0; row < height; row++)
{
buffer[(row + 1) * BufferStride] = left[row];
}
}
/// <summary>
/// Initializes a high-bit-depth recursive filter-intra workspace from the prepared top and left references.
/// </summary>
/// <param name="buffer">The recursive workspace.</param>
/// <param name="above">The top reference.</param>
/// <param name="left">The left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="topLeft">The shared top-left reference.</param>
private static void Initialize(Span<short> buffer, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, short topLeft)
{
buffer[0] = topLeft;
above[..width].CopyTo(buffer[1..]);
for (int row = 0; row < height; row++)
{
buffer[(row + 1) * BufferStride] = left[row];
}
}
/// <summary>
/// Creates the coefficients for eight output samples at one tap position.
/// </summary>
/// <param name="taps">The first coefficient in the operator table.</param>
/// <param name="tap">The tap position.</param>
/// <returns>The coefficient vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> CreateTapVector256(ref sbyte taps, int tap)
=> Vector256.Create(
(int)Unsafe.Add(ref taps, tap),
Unsafe.Add(ref taps, 7 + tap),
Unsafe.Add(ref taps, 14 + tap),
Unsafe.Add(ref taps, 21 + tap),
Unsafe.Add(ref taps, 28 + tap),
Unsafe.Add(ref taps, 35 + tap),
Unsafe.Add(ref taps, 42 + tap),
Unsafe.Add(ref taps, 49 + tap));
/// <summary>
/// Creates the coefficients for four output samples at one tap position.
/// </summary>
/// <param name="taps">The first coefficient in the operator table.</param>
/// <param name="tap">The tap position.</param>
/// <param name="pixel">The first output sample.</param>
/// <returns>The coefficient vector.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> CreateTapVector128(ref sbyte taps, int tap, int pixel)
=> Vector128.Create(
(int)Unsafe.Add(ref taps, (pixel * 7) + tap),
Unsafe.Add(ref taps, ((pixel + 1) * 7) + tap),
Unsafe.Add(ref taps, ((pixel + 2) * 7) + tap),
Unsafe.Add(ref taps, ((pixel + 3) * 7) + tap));
/// <summary>
/// Calculates eight filtered predictions from seven reconstructed samples.
/// </summary>
/// <param name="tap0">The first tap coefficients.</param>
/// <param name="tap1">The second tap coefficients.</param>
/// <param name="tap2">The third tap coefficients.</param>
/// <param name="tap3">The fourth tap coefficients.</param>
/// <param name="tap4">The fifth tap coefficients.</param>
/// <param name="tap5">The sixth tap coefficients.</param>
/// <param name="tap6">The seventh tap coefficients.</param>
/// <param name="p0">The first reconstructed sample.</param>
/// <param name="p1">The second reconstructed sample.</param>
/// <param name="p2">The third reconstructed sample.</param>
/// <param name="p3">The fourth reconstructed sample.</param>
/// <param name="p4">The fifth reconstructed sample.</param>
/// <param name="p5">The sixth reconstructed sample.</param>
/// <param name="p6">The seventh reconstructed sample.</param>
/// <returns>The unrounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Calculate(
Vector256<int> tap0,
Vector256<int> tap1,
Vector256<int> tap2,
Vector256<int> tap3,
Vector256<int> tap4,
Vector256<int> tap5,
Vector256<int> tap6,
int p0,
int p1,
int p2,
int p3,
int p4,
int p5,
int p6)
=> (tap0 * p0) + (tap1 * p1) + (tap2 * p2) + (tap3 * p3) + (tap4 * p4) + (tap5 * p5) + (tap6 * p6);
/// <summary>
/// Calculates four filtered predictions from seven reconstructed samples.
/// </summary>
/// <param name="tap0">The first tap coefficients.</param>
/// <param name="tap1">The second tap coefficients.</param>
/// <param name="tap2">The third tap coefficients.</param>
/// <param name="tap3">The fourth tap coefficients.</param>
/// <param name="tap4">The fifth tap coefficients.</param>
/// <param name="tap5">The sixth tap coefficients.</param>
/// <param name="tap6">The seventh tap coefficients.</param>
/// <param name="p0">The first reconstructed sample.</param>
/// <param name="p1">The second reconstructed sample.</param>
/// <param name="p2">The third reconstructed sample.</param>
/// <param name="p3">The fourth reconstructed sample.</param>
/// <param name="p4">The fifth reconstructed sample.</param>
/// <param name="p5">The sixth reconstructed sample.</param>
/// <param name="p6">The seventh reconstructed sample.</param>
/// <returns>The unrounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Calculate(
Vector128<int> tap0,
Vector128<int> tap1,
Vector128<int> tap2,
Vector128<int> tap3,
Vector128<int> tap4,
Vector128<int> tap5,
Vector128<int> tap6,
int p0,
int p1,
int p2,
int p3,
int p4,
int p5,
int p6)
=> (tap0 * p0) + (tap1 * p1) + (tap2 * p2) + (tap3 * p3) + (tap4 * p4) + (tap5 * p5) + (tap6 * p6);
/// <summary>
/// Stores eight 8-bit predictions into the two recursive output rows.
/// </summary>
/// <param name="prediction">The clamped predictions.</param>
/// <param name="buffer">The recursive workspace origin.</param>
/// <param name="firstRowOffset">The first output-row offset.</param>
/// <param name="secondRowOffset">The second output-row offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreEightBytes(Vector256<int> prediction, ref byte buffer, int firstRowOffset, int secondRowOffset)
{
Vector256<ushort> narrowed16 = Vector256.Narrow(prediction.AsUInt32(), Vector256<uint>.Zero);
Vector256<byte> narrowed8 = Vector256.Narrow(narrowed16, Vector256<ushort>.Zero);
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, firstRowOffset)) = narrowed8.AsUInt32().GetElement(0);
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, secondRowOffset)) = narrowed8.AsUInt32().GetElement(1);
}
/// <summary>
/// Stores four 8-bit predictions into one recursive output row.
/// </summary>
/// <param name="prediction">The clamped predictions.</param>
/// <param name="buffer">The recursive workspace origin.</param>
/// <param name="offset">The output-row offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourBytes(Vector128<int> prediction, ref byte buffer, int offset)
{
Vector128<ushort> narrowed16 = Vector128.Narrow(prediction.AsUInt32(), Vector128<uint>.Zero);
Vector128<byte> narrowed8 = Vector128.Narrow(narrowed16, Vector128<ushort>.Zero);
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, offset)) = narrowed8.AsUInt32().GetElement(0);
}
/// <summary>
/// Stores eight high-bit-depth predictions into the two recursive output rows.
/// </summary>
/// <param name="prediction">The clamped predictions.</param>
/// <param name="buffer">The recursive workspace origin.</param>
/// <param name="firstRowOffset">The first output-row offset.</param>
/// <param name="secondRowOffset">The second output-row offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreEightShorts(Vector256<int> prediction, ref short buffer, int firstRowOffset, int secondRowOffset)
{
Vector256<short> narrowed = Vector256.Narrow(prediction, Vector256<int>.Zero);
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, firstRowOffset)) = narrowed.AsUInt64().GetElement(0);
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, secondRowOffset)) = narrowed.AsUInt64().GetElement(1);
}
/// <summary>
/// Stores four high-bit-depth predictions into one recursive output row.
/// </summary>
/// <param name="prediction">The clamped predictions.</param>
/// <param name="buffer">The recursive workspace origin.</param>
/// <param name="offset">The output-row offset.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void StoreFourShorts(Vector128<int> prediction, ref short buffer, int offset)
{
Vector128<short> narrowed = Vector128.Narrow(prediction, Vector128<int>.Zero);
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, offset)) = narrowed.AsUInt64().GetElement(0);
}
/// <summary>
/// Calculates every 8-bit recursive filter group without hardware intrinsics.
/// </summary>
/// <param name="buffer">The initialized recursive workspace.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="maximum">The maximum reconstructed sample.</param>
/// <param name="taps">The first filter coefficient.</param>
private static void PredictGroupsScalar(Span<byte> buffer, int width, int height, int maximum, ref sbyte taps)
{
ref byte bufferBase = ref MemoryMarshal.GetReference(buffer);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferBase, sourceOffset);
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride);
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride));
for (int pixel = 0; pixel < 8; pixel++)
{
int tapOffset = pixel * 7;
int prediction =
(Unsafe.Add(ref taps, tapOffset) * p0)
+ (Unsafe.Add(ref taps, tapOffset + 1) * p1)
+ (Unsafe.Add(ref taps, tapOffset + 2) * p2)
+ (Unsafe.Add(ref taps, tapOffset + 3) * p3)
+ (Unsafe.Add(ref taps, tapOffset + 4) * p4)
+ (Unsafe.Add(ref taps, tapOffset + 5) * p5)
+ (Unsafe.Add(ref taps, tapOffset + 6) * p6);
int destinationOffset = ((row + (pixel >> 2)) * BufferStride) + column + (pixel & 3);
Unsafe.Add(ref bufferBase, destinationOffset) = (byte)Math.Clamp((prediction + 8) >> 4, 0, maximum);
}
}
}
}
/// <summary>
/// Calculates every high-bit-depth recursive filter group without hardware intrinsics.
/// </summary>
/// <param name="buffer">The initialized recursive workspace.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="maximum">The maximum reconstructed sample.</param>
/// <param name="taps">The first filter coefficient.</param>
private static void PredictGroupsScalar(Span<short> buffer, int width, int height, int maximum, ref sbyte taps)
{
ref short bufferBase = ref MemoryMarshal.GetReference(buffer);
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferBase, sourceOffset);
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride);
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride));
for (int pixel = 0; pixel < 8; pixel++)
{
int tapOffset = pixel * 7;
int prediction =
(Unsafe.Add(ref taps, tapOffset) * p0)
+ (Unsafe.Add(ref taps, tapOffset + 1) * p1)
+ (Unsafe.Add(ref taps, tapOffset + 2) * p2)
+ (Unsafe.Add(ref taps, tapOffset + 3) * p3)
+ (Unsafe.Add(ref taps, tapOffset + 4) * p4)
+ (Unsafe.Add(ref taps, tapOffset + 5) * p5)
+ (Unsafe.Add(ref taps, tapOffset + 6) * p6);
int destinationOffset = ((row + (pixel >> 2)) * BufferStride) + column + (pixel & 3);
Unsafe.Add(ref bufferBase, destinationOffset) = (short)Math.Clamp((prediction + 8) >> 4, 0, maximum);
}
}
}
}
/// <summary>
/// Copies an 8-bit recursive workspace into the strided destination.
/// </summary>
/// <param name="buffer">The completed recursive workspace.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void CopyToDestination(ReadOnlySpan<byte> buffer, Span<byte> destination, int destinationStride, int width, int height)
{
for (int row = 0; row < height; row++)
{
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo(destination.Slice(row * destinationStride, width));
}
}
/// <summary>
/// Copies a high-bit-depth recursive workspace into the strided destination.
/// </summary>
/// <param name="buffer">The completed recursive workspace.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void CopyToDestination(ReadOnlySpan<short> buffer, Span<short> destination, int destinationStride, int width, int height)
{
for (int row = 0; row < height; row++)
{
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo(destination.Slice(row * destinationStride, width));
}
}
/// <summary>
/// Copies an 8-bit recursive workspace into the strided destination without vectorized span copying.
/// </summary>
/// <param name="buffer">The completed recursive workspace.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void CopyToDestinationScalar(ReadOnlySpan<byte> buffer, Span<byte> destination, int destinationStride, int width, int height)
{
for (int row = 0; row < height; row++)
{
int sourceOffset = ((row + 1) * BufferStride) + 1;
int destinationOffset = row * destinationStride;
for (int column = 0; column < width; column++)
{
destination[destinationOffset + column] = buffer[sourceOffset + column];
}
}
}
/// <summary>
/// Copies a high-bit-depth recursive workspace into the strided destination without vectorized span copying.
/// </summary>
/// <param name="buffer">The completed recursive workspace.</param>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
private static void CopyToDestinationScalar(ReadOnlySpan<short> buffer, Span<short> destination, int destinationStride, int width, int height)
{
for (int row = 0; row < height; row++)
{
int sourceOffset = ((row + 1) * BufferStride) + 1;
int destinationOffset = row * destinationStride;
for (int column = 0; column < width; column++)
{
destination[destinationOffset + column] = buffer[sourceOffset + column];
}
}
}
}
}

25
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.OperatorContract.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the coefficient set for one AV1 filter-intra prediction mode.
/// </summary>
internal interface IAv1FilterIntraPredictionOperator
{
/// <summary>
/// Gets the filter-intra mode implemented by the operator.
/// </summary>
public static abstract Av1FilterIntraMode Mode { get; }
/// <summary>
/// Gets the eight seven-tap coefficient rows used by the operator.
/// </summary>
public static abstract ReadOnlySpan<sbyte> Taps { get; }
}
}

31
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.PaethOperator.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the AV1 Paeth filter-intra coefficient operator.
/// </summary>
internal readonly struct PaethOperator : IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Paeth;
/// <inheritdoc/>
public static ReadOnlySpan<sbyte> Taps =>
[
-12, 14, 0, 0, 0, 14, 0,
-10, 0, 14, 0, 0, 12, 0,
-9, 0, 0, 14, 0, 11, 0,
-8, 0, 0, 0, 14, 10, 0,
-10, 12, 0, 0, 0, 0, 14,
-9, 1, 12, 0, 0, 0, 12,
-8, 0, 0, 12, 0, 1, 11,
-7, 0, 0, 1, 12, 1, 9,
];
}
}

31
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.VerticalOperator.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// Defines the AV1 vertical filter-intra coefficient operator.
/// </summary>
internal readonly struct VerticalOperator : IAv1FilterIntraPredictionOperator
{
/// <inheritdoc/>
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Vertical;
/// <inheritdoc/>
public static ReadOnlySpan<sbyte> Taps =>
[
-10, 16, 0, 0, 0, 10, 0,
-6, 0, 16, 0, 0, 6, 0,
-4, 0, 0, 16, 0, 4, 0,
-2, 0, 0, 0, 16, 2, 0,
-10, 16, 0, 0, 0, 0, 10,
-6, 0, 16, 0, 0, 0, 6,
-4, 0, 0, 16, 0, 0, 4,
-2, 0, 0, 0, 16, 0, 2,
];
}
}

191
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.cs

@ -1,191 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Produces AV1 filter intra predictions from reconstructed neighboring samples.
/// </summary>
/// <remarks>
/// The scalar prediction follows the filter intra prediction process in section 7.11.2.3 of the AV1 specification.
/// </remarks>
internal static class Av1FilterIntraPredictor
{
/// <summary>
/// The row stride of the temporary prediction buffer.
/// </summary>
private const int BufferStride = 33;
/// <summary>
/// The number of samples in the temporary prediction buffer.
/// </summary>
private const int BufferLength = BufferStride * BufferStride;
/// <summary>
/// The number of nonzero filter coefficients used to predict each sample.
/// </summary>
private const int TapsPerPixel = 7;
/// <summary>
/// The number of samples produced by each filter coefficient group.
/// </summary>
private const int PixelsPerGroup = 8;
/// <summary>
/// The number of stored coefficients for each filter intra mode.
/// </summary>
private const int TapsPerMode = TapsPerPixel * PixelsPerGroup;
/// <summary>
/// Gets the filter coefficients for the five AV1 filter intra modes.
/// </summary>
/// <remarks>
/// AV1 defines eight filters per mode over seven nonzero neighboring samples. The eighth coefficient used by
/// libaom is always zero, so it is omitted here to keep the scalar coefficient layout aligned with the work performed.
/// </remarks>
public static readonly sbyte[] Taps =
[
// DC
-6, 10, 0, 0, 0, 12, 0,
-5, 2, 10, 0, 0, 9, 0,
-3, 1, 1, 10, 0, 7, 0,
-3, 1, 1, 2, 10, 5, 0,
-4, 6, 0, 0, 0, 2, 12,
-3, 2, 6, 0, 0, 2, 9,
-3, 2, 2, 6, 0, 2, 7,
-3, 1, 2, 2, 6, 3, 5,
// Vertical
-10, 16, 0, 0, 0, 10, 0,
-6, 0, 16, 0, 0, 6, 0,
-4, 0, 0, 16, 0, 4, 0,
-2, 0, 0, 0, 16, 2, 0,
-10, 16, 0, 0, 0, 0, 10,
-6, 0, 16, 0, 0, 0, 6,
-4, 0, 0, 16, 0, 0, 4,
-2, 0, 0, 0, 16, 0, 2,
// Horizontal
-8, 8, 0, 0, 0, 16, 0,
-8, 0, 8, 0, 0, 16, 0,
-8, 0, 0, 8, 0, 16, 0,
-8, 0, 0, 0, 8, 16, 0,
-4, 4, 0, 0, 0, 0, 16,
-4, 0, 4, 0, 0, 0, 16,
-4, 0, 0, 4, 0, 0, 16,
-4, 0, 0, 0, 4, 0, 16,
// Directional 157 degrees
-2, 8, 0, 0, 0, 10, 0,
-1, 3, 8, 0, 0, 6, 0,
-1, 2, 3, 8, 0, 4, 0,
0, 1, 2, 3, 8, 2, 0,
-1, 4, 0, 0, 0, 3, 10,
-1, 3, 4, 0, 0, 4, 6,
-1, 2, 3, 4, 0, 4, 4,
-1, 2, 2, 3, 4, 3, 3,
// Paeth
-12, 14, 0, 0, 0, 14, 0,
-10, 0, 14, 0, 0, 12, 0,
-9, 0, 0, 14, 0, 11, 0,
-8, 0, 0, 0, 14, 10, 0,
-10, 12, 0, 0, 0, 0, 14,
-9, 1, 12, 0, 0, 0, 12,
-8, 0, 0, 12, 0, 1, 11,
-7, 0, 0, 1, 12, 1, 9,
];
/// <summary>
/// Produces an 8-bit filter intra prediction for a transform block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="mode">The filter intra mode whose coefficient set is applied.</param>
public static void Predict(
Span<byte> destination,
nuint destinationStride,
Av1TransformSize transformSize,
Span<byte> above,
Span<byte> left,
Av1FilterIntraMode mode)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width));
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height));
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride));
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination));
Guard.MustBeSizedAtLeast(above, width, nameof(above));
Guard.MustBeSizedAtLeast(left, height, nameof(left));
Span<byte> buffer = stackalloc byte[BufferLength];
ref byte bufferRef = ref buffer[0];
ref byte aboveRef = ref above[0];
ref byte leftRef = ref left[0];
// Row zero includes the top-left sample followed by the top neighbors.
// Column zero stores the left neighbors so each 4-by-2 group can consume the
// seven already-reconstructed samples defined by the recursive AV1 process.
bufferRef = Unsafe.Subtract(ref aboveRef, 1);
above[..width].CopyTo(buffer[1..]);
for (int row = 0; row < height; row++)
{
Unsafe.Add(ref bufferRef, (row + 1) * BufferStride) = Unsafe.Add(ref leftRef, row);
}
int modeOffset = (int)mode * TapsPerMode;
ref sbyte tapsRef = ref Taps[modeOffset];
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * BufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferRef, sourceOffset);
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + BufferStride);
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * BufferStride));
for (int pixel = 0; pixel < PixelsPerGroup; pixel++)
{
int tapOffset = pixel * TapsPerPixel;
int prediction =
(Unsafe.Add(ref tapsRef, tapOffset) * p0)
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1)
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2)
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3)
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4)
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5)
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6);
int rowOffset = pixel >> 2;
int columnOffset = pixel & 3;
int destinationOffset = ((row + rowOffset) * BufferStride) + column + columnOffset;
Unsafe.Add(ref bufferRef, destinationOffset) =
(byte)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, 255);
}
}
}
ref byte destinationRef = ref destination[0];
for (int row = 0; row < height; row++)
{
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo(
MemoryMarshal.CreateSpan(ref destinationRef, width));
destinationRef = ref Unsafe.Add(ref destinationRef, destinationStride);
}
}
}

121
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictorBase.cs

@ -0,0 +1,121 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Reconstructs AV1 filter-intra prediction blocks from prepared neighboring samples.
/// </summary>
/// <remarks>
/// The implementation follows the recursive filter-intra process in section 7.11.2.3 of the AV1 specification.
/// </remarks>
internal abstract partial class Av1FilterIntraPredictorBase
{
/// <summary>
/// The row stride of the recursive prediction workspace.
/// </summary>
public const int BufferStride = 33;
/// <summary>
/// The number of samples in the recursive prediction workspace.
/// </summary>
public const int ScratchLength = BufferStride * BufferStride;
/// <summary>
/// The DC filter-intra predictor.
/// </summary>
private static readonly Av1FilterIntraPredictor<DcOperator> DcPredictor = new();
/// <summary>
/// The vertical filter-intra predictor.
/// </summary>
private static readonly Av1FilterIntraPredictor<VerticalOperator> VerticalPredictor = new();
/// <summary>
/// The horizontal filter-intra predictor.
/// </summary>
private static readonly Av1FilterIntraPredictor<HorizontalOperator> HorizontalPredictor = new();
/// <summary>
/// The 157-degree directional filter-intra predictor.
/// </summary>
private static readonly Av1FilterIntraPredictor<Directional157Operator> Directional157Predictor = new();
/// <summary>
/// The Paeth filter-intra predictor.
/// </summary>
private static readonly Av1FilterIntraPredictor<PaethOperator> PaethPredictor = new();
/// <summary>
/// Gets the filter-intra mode implemented by this predictor.
/// </summary>
public abstract Av1FilterIntraMode Mode { get; }
/// <summary>
/// Gets the closed predictor for a filter-intra mode.
/// </summary>
/// <param name="mode">The decoded filter-intra mode.</param>
/// <returns>The predictor for <paramref name="mode"/>.</returns>
public static Av1FilterIntraPredictorBase GetPredictor(Av1FilterIntraMode mode)
=> mode switch
{
Av1FilterIntraMode.DC => DcPredictor,
Av1FilterIntraMode.Vertical => VerticalPredictor,
Av1FilterIntraMode.Horizontal => HorizontalPredictor,
Av1FilterIntraMode.Directional157 => Directional157Predictor,
Av1FilterIntraMode.Paeth => PaethPredictor,
_ => throw new InvalidImageContentException($"Filter-intra mode {mode} is not defined by AV1."),
};
/// <summary>
/// Predicts an 8-bit filter-intra block.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
public abstract void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch);
/// <summary>
/// Predicts a high-bit-depth filter-intra block.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="bitDepth">The reconstructed sample precision.</param>
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
public abstract void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch);
/// <summary>
/// Predicts an 8-bit filter-intra block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
public abstract void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch);
/// <summary>
/// Predicts a high-bit-depth filter-intra block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width.</param>
/// <param name="height">The block height.</param>
/// <param name="bitDepth">The reconstructed sample precision.</param>
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
public abstract void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch);
}

602
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HighBitDepthPredictor.cs

@ -1,602 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Implements AV1 intra prediction for 10-bit and 12-bit sample buffers.
/// </summary>
/// <remarks>
/// Samples are stored in signed 16-bit buffers, but predictions are clamped to the nonnegative range of the signaled bit depth.
/// </remarks>
internal static class Av1HighBitDepthPredictor
{
/// <summary>
/// The row stride of the temporary filter intra prediction buffer.
/// </summary>
private const int FilterBufferStride = 33;
/// <summary>
/// The number of samples in the temporary filter intra prediction buffer.
/// </summary>
private const int FilterBufferLength = FilterBufferStride * FilterBufferStride;
/// <summary>
/// The number of nonzero filter coefficients used to predict each filter intra sample.
/// </summary>
private const int FilterTapsPerPixel = 7;
/// <summary>
/// The number of samples produced by each filter coefficient group.
/// </summary>
private const int FilterPixelsPerGroup = 8;
/// <summary>
/// The number of stored coefficients for each filter intra mode.
/// </summary>
private const int FilterTapsPerMode = FilterTapsPerPixel * FilterPixelsPerGroup;
/// <summary>
/// Produces a high-bit-depth DC intra prediction from the available neighboring samples.
/// </summary>
/// <param name="hasLeft">A value indicating whether reconstructed left samples are available.</param>
/// <param name="hasAbove">A value indicating whether reconstructed top samples are available.</param>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
public static void DcPredictor(
bool hasLeft,
bool hasAbove,
Av1TransformSize transformSize,
Span<short> destination,
nuint destinationStride,
Span<short> above,
Span<short> left,
int bitDepth)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
int sum = 0;
int count = 0;
if (hasAbove)
{
for (int column = 0; column < width; column++)
{
sum += above[column];
}
count += width;
}
if (hasLeft)
{
for (int row = 0; row < height; row++)
{
sum += left[row];
}
count += height;
}
short prediction = count == 0
? (short)(1 << (bitDepth - 1))
: (short)((sum + (count >> 1)) / count);
// The midpoint is normative when neither edge exists; otherwise the half-count bias
// rounds the mean of the available top and left samples to the nearest integer.
Fill(destination, destinationStride, width, height, prediction);
}
/// <summary>
/// Produces a high-bit-depth nondirectional intra prediction.
/// </summary>
/// <param name="mode">The nondirectional prediction mode to apply.</param>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
public static void GeneralPredictor(
Av1PredictionMode mode,
Av1TransformSize transformSize,
Span<short> destination,
nuint destinationStride,
Span<short> above,
Span<short> left)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
switch (mode)
{
case Av1PredictionMode.Horizontal:
PredictHorizontal(destination, destinationStride, left, width, height);
break;
case Av1PredictionMode.Vertical:
PredictVertical(destination, destinationStride, above, width, height);
break;
case Av1PredictionMode.Paeth:
PredictPaeth(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.Smooth:
PredictSmooth(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.SmoothHorizontal:
PredictSmoothHorizontal(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.SmoothVertical:
PredictSmoothVertical(destination, destinationStride, above, left, width, height);
break;
}
}
/// <summary>
/// Produces a high-bit-depth directional intra prediction.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="angle">The prediction angle in degrees.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
public static void DirectionalPredictor(
Span<short> destination,
nuint destinationStride,
Av1TransformSize transformSize,
Span<short> above,
Span<short> left,
bool upsampleAbove,
bool upsampleLeft,
int angle,
int bitDepth)
{
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle));
int dx = Av1PredictorFactory.GetDeltaX(angle);
int dy = Av1PredictorFactory.GetDeltaY(angle);
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
// Angles on a cardinal axis copy one reference edge directly. Other angles are
// separated into the three AV1 projection zones according to the edges they cross.
if (angle is > 0 and < 90)
{
PredictDirectionalZone1(destination, destinationStride, above, upsampleAbove, dx, width, height, bitDepth);
}
else if (angle is > 90 and < 180)
{
PredictDirectionalZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, dx, dy, width, height, bitDepth);
}
else if (angle is > 180 and < 270)
{
PredictDirectionalZone3(destination, destinationStride, left, upsampleLeft, dx, dy, width, height, bitDepth);
}
else if (angle == 90)
{
PredictVertical(destination, destinationStride, above, width, height);
}
else if (angle == 180)
{
PredictHorizontal(destination, destinationStride, left, width, height);
}
}
/// <summary>
/// Produces a high-bit-depth filter intra prediction.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="mode">The filter intra mode whose coefficient set is applied.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
public static void FilterIntraPredictor(
Span<short> destination,
nuint destinationStride,
Av1TransformSize transformSize,
Span<short> above,
Span<short> left,
Av1FilterIntraMode mode,
int bitDepth)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
int maximum = (1 << bitDepth) - 1;
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width));
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height));
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride));
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination));
Guard.MustBeSizedAtLeast(above, width, nameof(above));
Guard.MustBeSizedAtLeast(left, height, nameof(left));
Span<short> buffer = stackalloc short[FilterBufferLength];
ref short bufferRef = ref buffer[0];
ref short aboveRef = ref above[0];
ref short leftRef = ref left[0];
// Row zero includes the top-left sample followed by the top neighbors.
// Column zero stores the left neighbors so each 4-by-2 group can consume the
// seven already-reconstructed samples defined by the recursive AV1 process.
bufferRef = Unsafe.Subtract(ref aboveRef, 1);
above[..width].CopyTo(buffer[1..]);
for (int row = 0; row < height; row++)
{
Unsafe.Add(ref bufferRef, (row + 1) * FilterBufferStride) = Unsafe.Add(ref leftRef, row);
}
int modeOffset = (int)mode * FilterTapsPerMode;
ref sbyte tapsRef = ref Av1FilterIntraPredictor.Taps[modeOffset];
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * FilterBufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferRef, sourceOffset);
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + FilterBufferStride);
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * FilterBufferStride));
for (int pixel = 0; pixel < FilterPixelsPerGroup; pixel++)
{
int tapOffset = pixel * FilterTapsPerPixel;
int prediction =
(Unsafe.Add(ref tapsRef, tapOffset) * p0)
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1)
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2)
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3)
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4)
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5)
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6);
int rowOffset = pixel >> 2;
int columnOffset = pixel & 3;
int destinationOffset = ((row + rowOffset) * FilterBufferStride) + column + columnOffset;
Unsafe.Add(ref bufferRef, destinationOffset) =
(short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, maximum);
}
}
}
for (int row = 0; row < height; row++)
{
buffer.Slice(((row + 1) * FilterBufferStride) + 1, width).CopyTo(
destination.Slice(row * (int)destinationStride, width));
}
}
/// <summary>
/// Copies each left reference sample across one destination row.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictHorizontal(Span<short> destination, nuint stride, Span<short> left, int width, int height)
{
for (int row = 0; row < height; row++)
{
destination.Slice(row * (int)stride, width).Fill(left[row]);
}
}
/// <summary>
/// Copies the top reference samples into every destination row.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictVertical(Span<short> destination, nuint stride, Span<short> above, int width, int height)
{
for (int row = 0; row < height; row++)
{
above[..width].CopyTo(destination.Slice(row * (int)stride, width));
}
}
/// <summary>
/// Produces a Paeth prediction from the nearest top, left, and top-left reference sample.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictPaeth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int topLeft = Unsafe.Subtract(ref above[0], 1);
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
short leftValue = left[row];
short topValue = above[column];
int basis = topValue + leftValue - topLeft;
int leftDistance = Av1Math.AbsoluteDifference(basis, leftValue);
int topDistance = Av1Math.AbsoluteDifference(basis, topValue);
int topLeftDistance = Av1Math.AbsoluteDifference(basis, topLeft);
// The comparison order preserves AV1's left, top, then top-left tie precedence.
destinationRow[column] = leftDistance <= topDistance && leftDistance <= topLeftDistance
? leftValue
: topDistance <= topLeftDistance ? topValue : (short)topLeft;
}
}
}
/// <summary>
/// Produces a two-dimensional smooth prediction from the four terminating edge samples.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictSmooth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int below = left[height - 1];
int right = above[width - 1];
Span<int> widthWeights = Av1SmoothPredictor.Weights.AsSpan(width, width);
Span<int> heightWeights = Av1SmoothPredictor.Weights.AsSpan(height, height);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
int log2Scale = Av1SmoothPredictor.WeightLog2Scale + 1;
// Horizontal weights follow the block width and vertical weights follow the height.
// Keeping those domains separate is required for rectangular transform blocks.
for (int row = 0; row < height; row++)
{
int rowWeight = heightWeights[row];
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int columnWeight = widthWeights[column];
int prediction = (above[column] * rowWeight) + (below * (scale - rowWeight));
prediction += (left[row] * columnWeight) + (right * (scale - columnWeight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, log2Scale);
}
}
}
/// <summary>
/// Produces a horizontal smooth prediction between the left and right edge samples.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictSmoothHorizontal(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int right = above[width - 1];
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(width, width);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int weight = weights[column];
int prediction = (left[row] * weight) + (right * (scale - weight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale);
}
}
}
/// <summary>
/// Produces a vertical smooth prediction between the top and bottom edge samples.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The reconstructed top reference samples.</param>
/// <param name="left">The reconstructed left reference samples.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
private static void PredictSmoothVertical(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int below = left[height - 1];
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(height, height);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
for (int row = 0; row < height; row++)
{
int weight = weights[row];
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int prediction = (above[column] * weight) + (below * (scale - weight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale);
}
}
}
/// <summary>
/// Projects top reference samples into a directional zone 1 prediction block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="upsample">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
private static void PredictDirectionalZone1(Span<short> destination, nuint stride, Span<short> above, bool upsample, int dx, int width, int height, int bitDepth)
{
int upsampleAbove = upsample ? 1 : 0;
int maxBasisX = (width + height - 1) << upsampleAbove;
int fractionBitCount = 6 - upsampleAbove;
int basisIncrement = 1 << upsampleAbove;
int maximum = (1 << bitDepth) - 1;
int x = dx;
ref short aboveRef = ref above[0];
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
// AV1 retains six fractional projection bits, or five after reference upsampling.
// The interpolation weights sum to 32 because the low projection bit is discarded.
int basis = x >> fractionBitCount;
int shift = ((x << upsampleAbove) & 0x3F) >> 1;
for (int column = 0; column < width; column++)
{
if (basis < maxBasisX)
{
int prediction = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift);
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
else
{
destinationRow[column] = Unsafe.Add(ref aboveRef, maxBasisX);
}
basis += basisIncrement;
}
x += dx;
}
}
/// <summary>
/// Projects top and left reference samples into a directional zone 2 prediction block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top reference samples, including any required extension.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="doUpsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
/// <param name="doUpsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
private static void PredictDirectionalZone2(Span<short> destination, nuint stride, Span<short> above, Span<short> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy, int width, int height, int bitDepth)
{
int upsampleAbove = doUpsampleAbove ? 1 : 0;
int upsampleLeft = doUpsampleLeft ? 1 : 0;
int minBasisX = -(1 << upsampleAbove);
int fractionBitCountX = 6 - upsampleAbove;
int fractionBitCountY = 6 - upsampleLeft;
int basisIncrementX = 1 << upsampleAbove;
int maximum = (1 << bitDepth) - 1;
int x = -dx;
ref short aboveRef = ref above[0];
ref short leftRef = ref left[0];
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
int basisX = x >> fractionBitCountX;
int y = (row << 6) - dy;
for (int column = 0; column < width; column++, basisX += basisIncrementX, y -= dy)
{
int prediction;
// A nonnegative top projection uses the above edge. Once the projection crosses
// the top-left corner, the same destination sample is projected from the left edge.
if (basisX >= minBasisX)
{
int shift = ((x * (1 << upsampleAbove)) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref aboveRef, basisX) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basisX + 1) * shift);
}
else
{
int basisY = y >> fractionBitCountY;
int shift = ((y * (1 << upsampleLeft)) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref leftRef, basisY) * (32 - shift)) + (Unsafe.Add(ref leftRef, basisY + 1) * shift);
}
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
x -= dx;
}
}
/// <summary>
/// Projects left reference samples into a directional zone 3 prediction block.
/// </summary>
/// <param name="destination">The buffer that receives the predicted samples.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="left">The left reference samples, including any required extension.</param>
/// <param name="upsample">A value indicating whether the left reference samples were upsampled.</param>
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
private static void PredictDirectionalZone3(Span<short> destination, nuint stride, Span<short> left, bool upsample, int dx, int dy, int width, int height, int bitDepth)
{
int upsampleLeft = upsample ? 1 : 0;
int maxBasisY = (width + height - 1) << upsampleLeft;
int fractionBitCount = 6 - upsampleLeft;
int basisIncrement = 1 << upsampleLeft;
int maximum = (1 << bitDepth) - 1;
int y = dy;
ref short leftRef = ref left[0];
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction.");
for (int column = 0; column < width; column++)
{
// Zone 3 is the transpose of zone 1: columns advance along the projected left edge,
// while rows advance through the reference samples for each destination column.
int basis = y >> fractionBitCount;
int shift = ((y << upsampleLeft) & 0x3F) >> 1;
for (int row = 0; row < height; row++)
{
int destinationOffset = (row * (int)stride) + column;
if (basis < maxBasisY)
{
int prediction = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift);
destination[destinationOffset] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
else
{
destination[destinationOffset] = Unsafe.Add(ref leftRef, maxBasisY);
}
basis += basisIncrement;
}
y += dy;
}
}
/// <summary>
/// Fills a rectangular prediction block with one sample value.
/// </summary>
/// <param name="destination">The buffer that receives the sample value.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="width">The width of the prediction block in samples.</param>
/// <param name="height">The height of the prediction block in samples.</param>
/// <param name="value">The sample value written to every destination position.</param>
private static void Fill(Span<short> destination, nuint stride, int width, int height, short value)
{
for (int row = 0; row < height; row++)
{
destination.Slice(row * (int)stride, width).Fill(value);
}
}
}

72
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs

@ -1,72 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by extending each left neighboring sample across its destination row.
/// </summary>
internal readonly struct Av1HorizontalPredictor : IAv1Predictor
{
/// <summary>
/// The number of samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of left samples consumed and destination rows written.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1HorizontalPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1HorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1HorizontalPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1HorizontalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block by extending its left edge horizontally.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
/// <remarks>SVT-AV1: <c>highbd_h_predictor</c>.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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 destinationRef = ref destination[0];
uint width = (uint)this.blockWidth;
for (nuint r = 0; r < this.blockHeight; ++r)
{
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref leftRef, r), width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

54
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.HorizontalOperator.cs

@ -0,0 +1,54 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements AV1 horizontal intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct HorizontalOperator : IAv1IntraPredictionOperator
{
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.Horizontal;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs => Av1IntraPredictionInputs.Left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) => left;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) => left;
}
}

344
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.Operator.cs

@ -0,0 +1,344 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Applies one closed non-directional AV1 prediction operator using the widest available SIMD width.
/// </summary>
/// <typeparam name="TOperator">The prediction-mode-specific arithmetic.</typeparam>
internal sealed class Av1IntraPredictor<TOperator> : Av1IntraPredictorBase
where TOperator : struct, IAv1IntraPredictionOperator
{
/// <inheritdoc/>
public override Av1PredictionMode Mode => TOperator.Mode;
/// <inheritdoc/>
public override void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height)
{
ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
ref byte topBase = ref MemoryMarshal.GetReference(above);
ref byte leftBase = ref MemoryMarshal.GetReference(left);
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]);
Av1IntraPredictionInputs inputs = TOperator.Inputs;
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0;
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0;
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0;
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0;
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0;
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0;
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0;
byte topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default;
byte topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default;
byte bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default;
int processedColumns = 0;
if (Vector512.IsHardwareAccelerated)
{
int vectorizedColumns = width - (width % Vector512<byte>.Count);
if (vectorizedColumns > 0)
{
Vector512<byte> topLeftVector = usesTopLeft ? Vector512.Create(topLeft) : default;
Vector512<byte> topRightVector = usesTopRight ? Vector512.Create(topRight) : default;
Vector512<byte> bottomLeftVector = usesBottomLeft ? Vector512.Create(bottomLeft) : default;
for (int row = 0; row < height; row++)
{
Vector512<byte> leftVector = usesLeft ? Vector512.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = 0; column < vectorizedColumns; column += Vector512<byte>.Count)
{
Vector512<byte> top = usesTop ? Vector512.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector512<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = vectorizedColumns;
}
}
if (Vector256.IsHardwareAccelerated)
{
int remainingColumns = width - processedColumns;
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<byte>.Count);
if (vectorizedColumns > 0)
{
Vector256<byte> topLeftVector = usesTopLeft ? Vector256.Create(topLeft) : default;
Vector256<byte> topRightVector = usesTopRight ? Vector256.Create(topRight) : default;
Vector256<byte> bottomLeftVector = usesBottomLeft ? Vector256.Create(bottomLeft) : default;
int endColumn = processedColumns + vectorizedColumns;
for (int row = 0; row < height; row++)
{
Vector256<byte> leftVector = usesLeft ? Vector256.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < endColumn; column += Vector256<byte>.Count)
{
Vector256<byte> top = usesTop ? Vector256.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector256<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = endColumn;
}
}
if (Vector128.IsHardwareAccelerated)
{
int remainingColumns = width - processedColumns;
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<byte>.Count);
if (vectorizedColumns > 0)
{
Vector128<byte> topLeftVector = usesTopLeft ? Vector128.Create(topLeft) : default;
Vector128<byte> topRightVector = usesTopRight ? Vector128.Create(topRight) : default;
Vector128<byte> bottomLeftVector = usesBottomLeft ? Vector128.Create(bottomLeft) : default;
int endColumn = processedColumns + vectorizedColumns;
for (int row = 0; row < height; row++)
{
Vector128<byte> leftVector = usesLeft ? Vector128.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < endColumn; column += Vector128<byte>.Count)
{
Vector128<byte> top = usesTop ? Vector128.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector128<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = endColumn;
}
}
// AV1 dimensions are multiples of four. The tail is normally zero on SIMD hardware, but retaining the
// scalar continuation keeps the traversal correct when intrinsics are disabled by FeatureTestRunner.
for (int row = 0; row < height; row++)
{
byte leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < width; column++)
{
byte top = usesTop ? Unsafe.Add(ref topBase, column) : default;
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0;
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight);
}
}
}
/// <inheritdoc/>
public override void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height)
{
ref short destinationBase = ref MemoryMarshal.GetReference(destination);
ref short topBase = ref MemoryMarshal.GetReference(above);
ref short leftBase = ref MemoryMarshal.GetReference(left);
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]);
Av1IntraPredictionInputs inputs = TOperator.Inputs;
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0;
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0;
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0;
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0;
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0;
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0;
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0;
short topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default;
short topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default;
short bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default;
int processedColumns = 0;
if (Vector512.IsHardwareAccelerated)
{
int vectorizedColumns = width - (width % Vector512<short>.Count);
if (vectorizedColumns > 0)
{
Vector512<short> topLeftVector = usesTopLeft ? Vector512.Create(topLeft) : default;
Vector512<short> topRightVector = usesTopRight ? Vector512.Create(topRight) : default;
Vector512<short> bottomLeftVector = usesBottomLeft ? Vector512.Create(bottomLeft) : default;
for (int row = 0; row < height; row++)
{
Vector512<short> leftVector = usesLeft ? Vector512.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = 0; column < vectorizedColumns; column += Vector512<short>.Count)
{
Vector512<short> top = usesTop ? Vector512.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector512<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = vectorizedColumns;
}
}
if (Vector256.IsHardwareAccelerated)
{
int remainingColumns = width - processedColumns;
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<short>.Count);
if (vectorizedColumns > 0)
{
Vector256<short> topLeftVector = usesTopLeft ? Vector256.Create(topLeft) : default;
Vector256<short> topRightVector = usesTopRight ? Vector256.Create(topRight) : default;
Vector256<short> bottomLeftVector = usesBottomLeft ? Vector256.Create(bottomLeft) : default;
int endColumn = processedColumns + vectorizedColumns;
for (int row = 0; row < height; row++)
{
Vector256<short> leftVector = usesLeft ? Vector256.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < endColumn; column += Vector256<short>.Count)
{
Vector256<short> top = usesTop ? Vector256.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector256<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = endColumn;
}
}
if (Vector128.IsHardwareAccelerated)
{
int remainingColumns = width - processedColumns;
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<short>.Count);
if (vectorizedColumns > 0)
{
Vector128<short> topLeftVector = usesTopLeft ? Vector128.Create(topLeft) : default;
Vector128<short> topRightVector = usesTopRight ? Vector128.Create(topRight) : default;
Vector128<short> bottomLeftVector = usesBottomLeft ? Vector128.Create(bottomLeft) : default;
int endColumn = processedColumns + vectorizedColumns;
for (int row = 0; row < height; row++)
{
Vector128<short> leftVector = usesLeft ? Vector128.Create(Unsafe.Add(ref leftBase, row)) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < endColumn; column += Vector128<short>.Count)
{
Vector128<short> top = usesTop ? Vector128.LoadUnsafe(ref topBase, (nuint)column) : default;
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase;
Vector128<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight);
prediction.StoreUnsafe(ref destinationRow, (nuint)column);
}
}
processedColumns = endColumn;
}
}
for (int row = 0; row < height; row++)
{
short leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = processedColumns; column < width; column++)
{
short top = usesTop ? Unsafe.Add(ref topBase, column) : default;
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0;
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight);
}
}
}
/// <inheritdoc/>
public override void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height)
{
ref byte destinationBase = ref MemoryMarshal.GetReference(destination);
ref byte topBase = ref MemoryMarshal.GetReference(above);
ref byte leftBase = ref MemoryMarshal.GetReference(left);
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]);
Av1IntraPredictionInputs inputs = TOperator.Inputs;
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0;
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0;
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0;
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0;
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0;
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0;
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0;
byte topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default;
byte topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default;
byte bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default;
for (int row = 0; row < height; row++)
{
byte leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = 0; column < width; column++)
{
byte top = usesTop ? Unsafe.Add(ref topBase, column) : default;
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0;
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight);
}
}
}
/// <inheritdoc/>
public override void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height)
{
ref short destinationBase = ref MemoryMarshal.GetReference(destination);
ref short topBase = ref MemoryMarshal.GetReference(above);
ref short leftBase = ref MemoryMarshal.GetReference(left);
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]);
Av1IntraPredictionInputs inputs = TOperator.Inputs;
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0;
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0;
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0;
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0;
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0;
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0;
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0;
short topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default;
short topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default;
short bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default;
for (int row = 0; row < height; row++)
{
short leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default;
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0;
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride);
for (int column = 0; column < width; column++)
{
short top = usesTop ? Unsafe.Add(ref topBase, column) : default;
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0;
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight);
}
}
}
}
}

180
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.OperatorContract.cs

@ -0,0 +1,180 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.Intrinsics;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Identifies the neighboring inputs consumed by an AV1 intra-prediction operator.
/// </summary>
[Flags]
internal enum Av1IntraPredictionInputs
{
/// <summary>
/// The operator does not consume neighboring samples.
/// </summary>
None = 0,
/// <summary>
/// The operator consumes samples from the top reference.
/// </summary>
Top = 1,
/// <summary>
/// The operator consumes samples from the left reference.
/// </summary>
Left = 2,
/// <summary>
/// The operator consumes the shared top-left reference.
/// </summary>
TopLeft = 4,
/// <summary>
/// The operator consumes the final top reference.
/// </summary>
TopRight = 8,
/// <summary>
/// The operator consumes the final left reference.
/// </summary>
BottomLeft = 16,
/// <summary>
/// The operator consumes the horizontal smooth weights.
/// </summary>
ColumnWeight = 32,
/// <summary>
/// The operator consumes the vertical smooth weights.
/// </summary>
RowWeight = 64,
}
/// <summary>
/// Defines the scalar and SIMD arithmetic for one non-directional AV1 intra-prediction mode.
/// </summary>
/// <remarks>
/// Each overload performs the same lane-wise operation. The generic predictor traversal selects the widest
/// available overload, and the JIT specializes each static interface call for the closed operator type.
/// </remarks>
internal interface IAv1IntraPredictionOperator
{
/// <summary>
/// Gets the prediction mode implemented by the operator.
/// </summary>
public static abstract Av1PredictionMode Mode { get; }
/// <summary>
/// Gets the neighboring inputs consumed by the operator.
/// </summary>
public static abstract Av1IntraPredictionInputs Inputs { get; }
/// <summary>
/// Predicts one 8-bit sample when hardware vectorization is unavailable.
/// </summary>
/// <param name="top">The top reference sample.</param>
/// <param name="left">The left reference sample.</param>
/// <param name="topLeft">The shared top-left reference sample.</param>
/// <param name="topRight">The final top reference sample.</param>
/// <param name="bottomLeft">The final left reference sample.</param>
/// <param name="columnWeight">The horizontal Q8 smooth weight.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted sample.</returns>
public static abstract byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight);
/// <summary>
/// Predicts sixteen 8-bit samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight);
/// <summary>
/// Predicts thirty-two 8-bit samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight);
/// <summary>
/// Predicts sixty-four 8-bit samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight);
/// <summary>
/// Predicts one high-bit-depth sample when hardware vectorization is unavailable.
/// </summary>
/// <param name="top">The top reference sample.</param>
/// <param name="left">The left reference sample.</param>
/// <param name="topLeft">The shared top-left reference sample.</param>
/// <param name="topRight">The final top reference sample.</param>
/// <param name="bottomLeft">The final left reference sample.</param>
/// <param name="columnWeight">The horizontal Q8 smooth weight.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted sample.</returns>
public static abstract short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight);
/// <summary>
/// Predicts eight high-bit-depth samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight);
/// <summary>
/// Predicts sixteen high-bit-depth samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight);
/// <summary>
/// Predicts thirty-two high-bit-depth samples in parallel.
/// </summary>
/// <param name="top">The top reference samples.</param>
/// <param name="left">The left reference sample in every lane.</param>
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
/// <param name="topRight">The final top reference sample in every lane.</param>
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
/// <returns>The predicted samples.</returns>
public static abstract Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight);
}
}

166
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.Packing.cs

@ -0,0 +1,166 @@
// 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;
/// <summary>
/// Widens AV1 sample vectors for fixed-point prediction arithmetic and narrows completed results.
/// </summary>
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Widens sixteen 8-bit samples into four 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first four widened samples.</param>
/// <param name="result1">The second four widened samples.</param>
/// <param name="result2">The third four widened samples.</param>
/// <param name="result3">The fourth four widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector128<byte> source, out Vector128<int> result0, out Vector128<int> result1, out Vector128<int> result2, out Vector128<int> result3)
{
(Vector128<ushort> low, Vector128<ushort> high) = Vector128.Widen(source);
(Vector128<uint> low0, Vector128<uint> low1) = Vector128.Widen(low);
(Vector128<uint> high0, Vector128<uint> high1) = Vector128.Widen(high);
result0 = low0.AsInt32();
result1 = low1.AsInt32();
result2 = high0.AsInt32();
result3 = high1.AsInt32();
}
/// <summary>
/// Widens thirty-two 8-bit samples into four 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first eight widened samples.</param>
/// <param name="result1">The second eight widened samples.</param>
/// <param name="result2">The third eight widened samples.</param>
/// <param name="result3">The fourth eight widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector256<byte> source, out Vector256<int> result0, out Vector256<int> result1, out Vector256<int> result2, out Vector256<int> result3)
{
(Vector256<ushort> low, Vector256<ushort> high) = Vector256.Widen(source);
(Vector256<uint> low0, Vector256<uint> low1) = Vector256.Widen(low);
(Vector256<uint> high0, Vector256<uint> high1) = Vector256.Widen(high);
result0 = low0.AsInt32();
result1 = low1.AsInt32();
result2 = high0.AsInt32();
result3 = high1.AsInt32();
}
/// <summary>
/// Widens sixty-four 8-bit samples into four 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first sixteen widened samples.</param>
/// <param name="result1">The second sixteen widened samples.</param>
/// <param name="result2">The third sixteen widened samples.</param>
/// <param name="result3">The fourth sixteen widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector512<byte> source, out Vector512<int> result0, out Vector512<int> result1, out Vector512<int> result2, out Vector512<int> result3)
{
(Vector512<ushort> low, Vector512<ushort> high) = Vector512.Widen(source);
(Vector512<uint> low0, Vector512<uint> low1) = Vector512.Widen(low);
(Vector512<uint> high0, Vector512<uint> high1) = Vector512.Widen(high);
result0 = low0.AsInt32();
result1 = low1.AsInt32();
result2 = high0.AsInt32();
result3 = high1.AsInt32();
}
/// <summary>
/// Widens eight high-bit-depth samples into two 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first four widened samples.</param>
/// <param name="result1">The second four widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector128<short> source, out Vector128<int> result0, out Vector128<int> result1)
=> (result0, result1) = Vector128.Widen(source);
/// <summary>
/// Widens sixteen high-bit-depth samples into two 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first eight widened samples.</param>
/// <param name="result1">The second eight widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector256<short> source, out Vector256<int> result0, out Vector256<int> result1)
=> (result0, result1) = Vector256.Widen(source);
/// <summary>
/// Widens thirty-two high-bit-depth samples into two 32-bit vectors.
/// </summary>
/// <param name="source">The packed samples.</param>
/// <param name="result0">The first sixteen widened samples.</param>
/// <param name="result1">The second sixteen widened samples.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Widen(Vector512<short> source, out Vector512<int> result0, out Vector512<int> result1)
=> (result0, result1) = Vector512.Widen(source);
/// <summary>
/// Narrows four 32-bit vectors into sixteen 8-bit samples.
/// </summary>
/// <param name="source0">The first four samples.</param>
/// <param name="source1">The second four samples.</param>
/// <param name="source2">The third four samples.</param>
/// <param name="source3">The fourth four samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Narrow(Vector128<int> source0, Vector128<int> source1, Vector128<int> source2, Vector128<int> source3)
=> Vector128.Narrow(Vector128.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector128.Narrow(source2.AsUInt32(), source3.AsUInt32()));
/// <summary>
/// Narrows four 32-bit vectors into thirty-two 8-bit samples.
/// </summary>
/// <param name="source0">The first eight samples.</param>
/// <param name="source1">The second eight samples.</param>
/// <param name="source2">The third eight samples.</param>
/// <param name="source3">The fourth eight samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Narrow(Vector256<int> source0, Vector256<int> source1, Vector256<int> source2, Vector256<int> source3)
=> Vector256.Narrow(Vector256.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector256.Narrow(source2.AsUInt32(), source3.AsUInt32()));
/// <summary>
/// Narrows four 32-bit vectors into sixty-four 8-bit samples.
/// </summary>
/// <param name="source0">The first sixteen samples.</param>
/// <param name="source1">The second sixteen samples.</param>
/// <param name="source2">The third sixteen samples.</param>
/// <param name="source3">The fourth sixteen samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Narrow(Vector512<int> source0, Vector512<int> source1, Vector512<int> source2, Vector512<int> source3)
=> Vector512.Narrow(Vector512.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector512.Narrow(source2.AsUInt32(), source3.AsUInt32()));
/// <summary>
/// Narrows two 32-bit vectors into eight high-bit-depth samples.
/// </summary>
/// <param name="source0">The first four samples.</param>
/// <param name="source1">The second four samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Narrow(Vector128<int> source0, Vector128<int> source1) => Vector128.Narrow(source0, source1);
/// <summary>
/// Narrows two 32-bit vectors into sixteen high-bit-depth samples.
/// </summary>
/// <param name="source0">The first eight samples.</param>
/// <param name="source1">The second eight samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Narrow(Vector256<int> source0, Vector256<int> source1) => Vector256.Narrow(source0, source1);
/// <summary>
/// Narrows two 32-bit vectors into thirty-two high-bit-depth samples.
/// </summary>
/// <param name="source0">The first sixteen samples.</param>
/// <param name="source1">The second sixteen samples.</param>
/// <returns>The packed samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Narrow(Vector512<int> source0, Vector512<int> source1) => Vector512.Narrow(source0, source1);
}

209
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.PaethOperator.cs

@ -0,0 +1,209 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements AV1 Paeth intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct PaethOperator : IAv1IntraPredictionOperator
{
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.Paeth;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs
=> Av1IntraPredictionInputs.Top | Av1IntraPredictionInputs.Left | Av1IntraPredictionInputs.TopLeft;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight)
{
int basis = top + left - topLeft;
int distanceLeft = Math.Abs(basis - left);
int distanceTop = Math.Abs(basis - top);
int distanceTopLeft = Math.Abs(basis - topLeft);
// AV1 resolves equal distances in left, top, top-left order.
return distanceLeft <= distanceTop && distanceLeft <= distanceTopLeft ? left : distanceTop <= distanceTopLeft ? top : topLeft;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight)
{
int basis = top + left - topLeft;
int distanceLeft = Math.Abs(basis - left);
int distanceTop = Math.Abs(basis - top);
int distanceTopLeft = Math.Abs(basis - topLeft);
// High-bit-depth prediction has the same tie order as the 8-bit process.
return distanceLeft <= distanceTop && distanceLeft <= distanceTopLeft ? left : distanceTop <= distanceTopLeft ? top : topLeft;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight)
=> PredictPaeth(top, left, topLeft);
/// <summary>
/// Selects the nearest Paeth reference for sixteen 8-bit lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> PredictPaeth(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft)
{
// The Paeth distances to left and top simplify to |top - topLeft| and |left - topLeft|. The established
// PNG predictor uses the same byte-lane identity, avoiding four widening stages for every sixteen samples.
Vector128<byte> topMinusTopLeft = Vector128.SubtractSaturate(top, topLeft);
Vector128<byte> leftMinusTopLeft = Vector128.SubtractSaturate(left, topLeft);
Vector128<byte> distanceLeft = Vector128.SubtractSaturate(topLeft, top) | topMinusTopLeft;
Vector128<byte> distanceTop = Vector128.SubtractSaturate(topLeft, left) | leftMinusTopLeft;
Vector128<byte> sameDirection = Vector128.Equals(Vector128.Equals(topMinusTopLeft, Vector128<byte>.Zero), Vector128.Equals(leftMinusTopLeft, Vector128<byte>.Zero));
Vector128<byte> distanceTopLeft = sameDirection | Vector128.SubtractSaturate(distanceTop, distanceLeft) | Vector128.SubtractSaturate(distanceLeft, distanceTop);
Vector128<byte> minimumTopTopLeft = Vector128.Min(distanceTopLeft, distanceTop);
Vector128<byte> topOrTopLeft = Vector128.ConditionalSelect(Vector128.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
/// <summary>
/// Selects the nearest Paeth reference for thirty-two 8-bit lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> PredictPaeth(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft)
{
Vector256<byte> topMinusTopLeft = Vector256.SubtractSaturate(top, topLeft);
Vector256<byte> leftMinusTopLeft = Vector256.SubtractSaturate(left, topLeft);
Vector256<byte> distanceLeft = Vector256.SubtractSaturate(topLeft, top) | topMinusTopLeft;
Vector256<byte> distanceTop = Vector256.SubtractSaturate(topLeft, left) | leftMinusTopLeft;
Vector256<byte> sameDirection = Vector256.Equals(Vector256.Equals(topMinusTopLeft, Vector256<byte>.Zero), Vector256.Equals(leftMinusTopLeft, Vector256<byte>.Zero));
Vector256<byte> distanceTopLeft = sameDirection | Vector256.SubtractSaturate(distanceTop, distanceLeft) | Vector256.SubtractSaturate(distanceLeft, distanceTop);
Vector256<byte> minimumTopTopLeft = Vector256.Min(distanceTopLeft, distanceTop);
Vector256<byte> topOrTopLeft = Vector256.ConditionalSelect(Vector256.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
/// <summary>
/// Selects the nearest Paeth reference for sixty-four 8-bit lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<byte> PredictPaeth(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft)
{
Vector512<byte> topMinusTopLeft = Vector512.SubtractSaturate(top, topLeft);
Vector512<byte> leftMinusTopLeft = Vector512.SubtractSaturate(left, topLeft);
Vector512<byte> distanceLeft = Vector512.SubtractSaturate(topLeft, top) | topMinusTopLeft;
Vector512<byte> distanceTop = Vector512.SubtractSaturate(topLeft, left) | leftMinusTopLeft;
Vector512<byte> sameDirection = Vector512.Equals(Vector512.Equals(topMinusTopLeft, Vector512<byte>.Zero), Vector512.Equals(leftMinusTopLeft, Vector512<byte>.Zero));
Vector512<byte> distanceTopLeft = sameDirection | Vector512.SubtractSaturate(distanceTop, distanceLeft) | Vector512.SubtractSaturate(distanceLeft, distanceTop);
Vector512<byte> minimumTopTopLeft = Vector512.Min(distanceTopLeft, distanceTop);
Vector512<byte> topOrTopLeft = Vector512.ConditionalSelect(Vector512.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
/// <summary>
/// Selects the nearest Paeth reference for eight high-bit-depth lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<short> PredictPaeth(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft)
{
// AV1 samples are at most twelve bits. Both signed differences and their sum are therefore bounded by
// 8190, allowing the exact Paeth distances to stay in signed 16-bit lanes without widening.
Vector128<short> topDelta = top - topLeft;
Vector128<short> leftDelta = left - topLeft;
Vector128<short> distanceLeft = Vector128.Abs(topDelta);
Vector128<short> distanceTop = Vector128.Abs(leftDelta);
Vector128<short> distanceTopLeft = Vector128.Abs(topDelta + leftDelta);
Vector128<short> minimumTopTopLeft = Vector128.Min(distanceTopLeft, distanceTop);
Vector128<short> topOrTopLeft = Vector128.ConditionalSelect(Vector128.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
/// <summary>
/// Selects the nearest Paeth reference for sixteen high-bit-depth lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<short> PredictPaeth(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft)
{
Vector256<short> topDelta = top - topLeft;
Vector256<short> leftDelta = left - topLeft;
Vector256<short> distanceLeft = Vector256.Abs(topDelta);
Vector256<short> distanceTop = Vector256.Abs(leftDelta);
Vector256<short> distanceTopLeft = Vector256.Abs(topDelta + leftDelta);
Vector256<short> minimumTopTopLeft = Vector256.Min(distanceTopLeft, distanceTop);
Vector256<short> topOrTopLeft = Vector256.ConditionalSelect(Vector256.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
/// <summary>
/// Selects the nearest Paeth reference for thirty-two high-bit-depth lanes.
/// </summary>
/// <param name="top">The top candidates.</param>
/// <param name="left">The left candidates.</param>
/// <param name="topLeft">The top-left candidates.</param>
/// <returns>The selected candidates.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<short> PredictPaeth(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft)
{
Vector512<short> topDelta = top - topLeft;
Vector512<short> leftDelta = left - topLeft;
Vector512<short> distanceLeft = Vector512.Abs(topDelta);
Vector512<short> distanceTop = Vector512.Abs(leftDelta);
Vector512<short> distanceTopLeft = Vector512.Abs(topDelta + leftDelta);
Vector512<short> minimumTopTopLeft = Vector512.Min(distanceTopLeft, distanceTop);
Vector512<short> topOrTopLeft = Vector512.ConditionalSelect(Vector512.Equals(minimumTopTopLeft, distanceTop), top, topLeft);
return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft);
}
}
}

161
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothHorizontalOperator.cs

@ -0,0 +1,161 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements horizontal AV1 smooth intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct SmoothHorizontalOperator : IAv1IntraPredictionOperator
{
/// <summary>
/// The Q8 scale used by the smooth surface.
/// </summary>
private const int WeightScale = 256;
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.SmoothHorizontal;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs
=> Av1IntraPredictionInputs.Left | Av1IntraPredictionInputs.TopRight | Av1IntraPredictionInputs.ColumnWeight;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight)
=> (byte)(((left * columnWeight) + (topRight * (WeightScale - columnWeight)) + (WeightScale >> 1)) >> 8);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector128<int> endpointBias = Vector128.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector128.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count), horizontalDelta, endpointBias),
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector128<int>.Count)), horizontalDelta, endpointBias),
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector128<int>.Count)), horizontalDelta, endpointBias));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector256<int> endpointBias = Vector256.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector256.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count), horizontalDelta, endpointBias),
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector256<int>.Count)), horizontalDelta, endpointBias),
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector256<int>.Count)), horizontalDelta, endpointBias));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector512<int> endpointBias = Vector512.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector512.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count), horizontalDelta, endpointBias),
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector512<int>.Count)), horizontalDelta, endpointBias),
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector512<int>.Count)), horizontalDelta, endpointBias));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight)
=> (short)(((left * columnWeight) + (topRight * (WeightScale - columnWeight)) + (WeightScale >> 1)) >> 8);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector128<int> endpointBias = Vector128.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector128.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count), horizontalDelta, endpointBias));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector256<int> endpointBias = Vector256.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector256.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count), horizontalDelta, endpointBias));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight)
{
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector512<int> endpointBias = Vector512.Create((topRightSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(Vector512.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias),
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count), horizontalDelta, endpointBias));
}
/// <summary>
/// Calculates four horizontal smooth predictions.
/// </summary>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Calculate(Vector128<int> columnWeights, int horizontalDelta, Vector128<int> endpointBias)
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8;
/// <summary>
/// Calculates eight horizontal smooth predictions.
/// </summary>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Calculate(Vector256<int> columnWeights, int horizontalDelta, Vector256<int> endpointBias)
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8;
/// <summary>
/// Calculates sixteen horizontal smooth predictions.
/// </summary>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<int> Calculate(Vector512<int> columnWeights, int horizontalDelta, Vector512<int> endpointBias)
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8;
}
}

241
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothOperator.cs

@ -0,0 +1,241 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements two-dimensional AV1 smooth intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct SmoothOperator : IAv1IntraPredictionOperator
{
/// <summary>
/// The Q8 scale used by each smooth surface.
/// </summary>
private const int WeightScale = 256;
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.Smooth;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs
=> Av1IntraPredictionInputs.Top
| Av1IntraPredictionInputs.Left
| Av1IntraPredictionInputs.TopRight
| Av1IntraPredictionInputs.BottomLeft
| Av1IntraPredictionInputs.ColumnWeight
| Av1IntraPredictionInputs.RowWeight;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight)
{
int prediction = (top * rowWeight) + (bottomLeft * (WeightScale - rowWeight));
prediction += (left * columnWeight) + (topRight * (WeightScale - columnWeight));
return (byte)((prediction + WeightScale) >> 9);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector128<int> top0, out Vector128<int> top1, out Vector128<int> top2, out Vector128<int> top3);
Vector128<int> weight0 = Vector128.LoadUnsafe(ref columnWeights);
Vector128<int> weight1 = Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count);
Vector128<int> weight2 = Vector128.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector128<int>.Count));
Vector128<int> weight3 = Vector128.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector128<int>.Count));
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample);
Vector128<int> endpointBias = Vector128.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector256<int> top0, out Vector256<int> top1, out Vector256<int> top2, out Vector256<int> top3);
Vector256<int> weight0 = Vector256.LoadUnsafe(ref columnWeights);
Vector256<int> weight1 = Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count);
Vector256<int> weight2 = Vector256.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector256<int>.Count));
Vector256<int> weight3 = Vector256.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector256<int>.Count));
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample);
Vector256<int> endpointBias = Vector256.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector512<int> top0, out Vector512<int> top1, out Vector512<int> top2, out Vector512<int> top3);
Vector512<int> weight0 = Vector512.LoadUnsafe(ref columnWeights);
Vector512<int> weight1 = Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count);
Vector512<int> weight2 = Vector512.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector512<int>.Count));
Vector512<int> weight3 = Vector512.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector512<int>.Count));
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample);
Vector512<int> endpointBias = Vector512.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight)
{
int prediction = (top * rowWeight) + (bottomLeft * (WeightScale - rowWeight));
prediction += (left * columnWeight) + (topRight * (WeightScale - columnWeight));
return (short)((prediction + WeightScale) >> 9);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector128<int> top0, out Vector128<int> top1);
Vector128<int> weight0 = Vector128.LoadUnsafe(ref columnWeights);
Vector128<int> weight1 = Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count);
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample);
Vector128<int> endpointBias = Vector128.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector256<int> top0, out Vector256<int> top1);
Vector256<int> weight0 = Vector256.LoadUnsafe(ref columnWeights);
Vector256<int> weight1 = Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count);
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample);
Vector256<int> endpointBias = Vector256.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector512<int> top0, out Vector512<int> top1);
Vector512<int> weight0 = Vector512.LoadUnsafe(ref columnWeights);
Vector512<int> weight1 = Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count);
int leftSample = left.GetElement(0);
int topRightSample = topRight.GetElement(0);
int bottomLeftSample = bottomLeft.GetElement(0);
int horizontalDelta = leftSample - topRightSample;
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample);
Vector512<int> endpointBias = Vector512.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale);
return Narrow(
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight),
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight));
}
/// <summary>
/// Calculates four two-dimensional smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Calculate(
Vector128<int> top,
Vector128<int> columnWeights,
Vector128<int> bottomLeft,
int horizontalDelta,
Vector128<int> endpointBias,
int rowWeight)
{
// Expanding the complementary weights gives the exact normative sum while reducing it to two vector
// multiplications: (top - bottom) * rowWeight + (left - right) * columnWeight + the endpoint bias.
return (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9;
}
/// <summary>
/// Calculates eight two-dimensional smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Calculate(
Vector256<int> top,
Vector256<int> columnWeights,
Vector256<int> bottomLeft,
int horizontalDelta,
Vector256<int> endpointBias,
int rowWeight)
=> (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9;
/// <summary>
/// Calculates sixteen two-dimensional smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="columnWeights">The horizontal Q8 weights.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<int> Calculate(
Vector512<int> top,
Vector512<int> columnWeights,
Vector512<int> bottomLeft,
int horizontalDelta,
Vector512<int> endpointBias,
int rowWeight)
=> (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9;
}
}

164
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.SmoothVerticalOperator.cs

@ -0,0 +1,164 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements vertical AV1 smooth intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct SmoothVerticalOperator : IAv1IntraPredictionOperator
{
/// <summary>
/// The Q8 scale used by the smooth surface.
/// </summary>
private const int WeightScale = 256;
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.SmoothVertical;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs
=> Av1IntraPredictionInputs.Top | Av1IntraPredictionInputs.BottomLeft | Av1IntraPredictionInputs.RowWeight;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight)
=> (byte)(((top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)) + (WeightScale >> 1)) >> 8);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector128<int> top0, out Vector128<int> top1, out Vector128<int> top2, out Vector128<int> top3);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample);
Vector128<int> endpointBias = Vector128.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight),
Calculate(top2, bottomLeftVector, endpointBias, rowWeight),
Calculate(top3, bottomLeftVector, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector256<int> top0, out Vector256<int> top1, out Vector256<int> top2, out Vector256<int> top3);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample);
Vector256<int> endpointBias = Vector256.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight),
Calculate(top2, bottomLeftVector, endpointBias, rowWeight),
Calculate(top3, bottomLeftVector, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector512<int> top0, out Vector512<int> top1, out Vector512<int> top2, out Vector512<int> top3);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample);
Vector512<int> endpointBias = Vector512.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight),
Calculate(top2, bottomLeftVector, endpointBias, rowWeight),
Calculate(top3, bottomLeftVector, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight)
=> (short)(((top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)) + (WeightScale >> 1)) >> 8);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector128<int> top0, out Vector128<int> top1);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample);
Vector128<int> endpointBias = Vector128.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector256<int> top0, out Vector256<int> top1);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample);
Vector256<int> endpointBias = Vector256.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight)
{
Widen(top, out Vector512<int> top0, out Vector512<int> top1);
int bottomLeftSample = bottomLeft.GetElement(0);
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample);
Vector512<int> endpointBias = Vector512.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1));
return Narrow(
Calculate(top0, bottomLeftVector, endpointBias, rowWeight),
Calculate(top1, bottomLeftVector, endpointBias, rowWeight));
}
/// <summary>
/// Calculates four vertical smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Calculate(Vector128<int> top, Vector128<int> bottomLeft, Vector128<int> endpointBias, int rowWeight)
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8;
/// <summary>
/// Calculates eight vertical smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Calculate(Vector256<int> top, Vector256<int> bottomLeft, Vector256<int> endpointBias, int rowWeight)
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8;
/// <summary>
/// Calculates sixteen vertical smooth predictions.
/// </summary>
/// <param name="top">The widened top samples.</param>
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
/// <param name="rowWeight">The vertical Q8 weight.</param>
/// <returns>The rounded predictions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<int> Calculate(Vector512<int> top, Vector512<int> bottomLeft, Vector512<int> endpointBias, int rowWeight)
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8;
}
}

54
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictor.VerticalOperator.cs

@ -0,0 +1,54 @@
// 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;
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// Implements AV1 vertical intra prediction for scalar and SIMD lanes.
/// </summary>
internal readonly struct VerticalOperator : IAv1IntraPredictionOperator
{
/// <inheritdoc/>
public static Av1PredictionMode Mode => Av1PredictionMode.Vertical;
/// <inheritdoc/>
public static Av1IntraPredictionInputs Inputs => Av1IntraPredictionInputs.Top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) => top;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) => top;
}
}

129
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1IntraPredictorBase.cs

@ -0,0 +1,129 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Reconstructs non-directional AV1 intra-prediction blocks from prepared neighboring samples.
/// </summary>
/// <remarks>
/// The implementation covers the non-directional prediction processes in section 7.11.2 of the AV1 specification.
/// </remarks>
internal abstract partial class Av1IntraPredictorBase
{
/// <summary>
/// The horizontal prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<HorizontalOperator> HorizontalPredictor = new();
/// <summary>
/// The vertical prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<VerticalOperator> VerticalPredictor = new();
/// <summary>
/// The Paeth prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<PaethOperator> PaethPredictor = new();
/// <summary>
/// The two-dimensional smooth prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<SmoothOperator> SmoothPredictor = new();
/// <summary>
/// The horizontal smooth prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<SmoothHorizontalOperator> SmoothHorizontalPredictor = new();
/// <summary>
/// The vertical smooth prediction operator.
/// </summary>
private static readonly Av1IntraPredictor<SmoothVerticalOperator> SmoothVerticalPredictor = new();
/// <summary>
/// Gets the Q8 smooth weights for every supported block dimension.
/// </summary>
private static ReadOnlySpan<int> SmoothWeights =>
[
// The first two entries are unused because the smallest AV1 prediction dimension is four samples.
0, 0,
255, 128,
255, 149, 85, 64,
255, 197, 146, 105, 73, 50, 37, 32,
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16,
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74,
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8,
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156,
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69,
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15,
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4,
];
/// <summary>
/// Gets the prediction mode implemented by this predictor.
/// </summary>
public abstract Av1PredictionMode Mode { get; }
/// <summary>
/// Gets the closed predictor for a non-directional AV1 prediction mode.
/// </summary>
/// <param name="mode">The decoded non-directional prediction mode.</param>
/// <returns>The predictor for <paramref name="mode"/>.</returns>
public static Av1IntraPredictorBase GetPredictor(Av1PredictionMode mode)
=> mode switch
{
Av1PredictionMode.Horizontal => HorizontalPredictor,
Av1PredictionMode.Vertical => VerticalPredictor,
Av1PredictionMode.Paeth => PaethPredictor,
Av1PredictionMode.Smooth => SmoothPredictor,
Av1PredictionMode.SmoothHorizontal => SmoothHorizontalPredictor,
Av1PredictionMode.SmoothVertical => SmoothVerticalPredictor,
_ => throw new InvalidImageContentException($"Prediction mode {mode} is not a non-directional AV1 mode."),
};
/// <summary>
/// Predicts an 8-bit block.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public abstract void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height);
/// <summary>
/// Predicts a high-bit-depth block.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public abstract void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height);
/// <summary>
/// Predicts an 8-bit block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public abstract void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height);
/// <summary>
/// Predicts a high-bit-depth block without hardware intrinsics.
/// </summary>
/// <param name="destination">The destination block origin.</param>
/// <param name="destinationStride">The destination row stride in samples.</param>
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
/// <param name="left">The prepared left reference.</param>
/// <param name="width">The block width in samples.</param>
/// <param name="height">The block height in samples.</param>
public abstract void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height);
}

99
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs

@ -1,99 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by selecting the top, left, or top-left neighbor with the smallest local gradient.
/// </summary>
internal readonly struct Av1PaethPredictor : IAv1Predictor
{
/// <summary>
/// The number of top samples consumed and samples written to each destination row.
/// </summary>
private readonly uint blockWidth;
/// <summary>
/// The number of left samples consumed and destination rows written.
/// </summary>
private readonly uint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1PaethPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1PaethPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1PaethPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1PaethPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block using the Paeth gradient selector.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples, preceded in memory by the top-left sample.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1PaethPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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];
// The caller preserves the top-left sample immediately before the top row.
int yTopLeft = Unsafe.Subtract(ref aboveRef, 1);
ref byte destinationRef = ref destination[0];
for (nuint r = 0; r < this.blockHeight; r++)
{
for (nuint c = 0; c < this.blockWidth; c++)
{
Unsafe.Add(ref destinationRef, c) = PredictSingle(
Unsafe.Add(ref leftRef, r),
Unsafe.Add(ref aboveRef, c),
yTopLeft);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
/// <summary>
/// Selects the reference sample nearest to the planar estimate <paramref name="top"/> + <paramref name="left"/> - <paramref name="topLeft"/>.
/// </summary>
/// <param name="left">The left reference sample.</param>
/// <param name="top">The top reference sample.</param>
/// <param name="topLeft">The shared top-left reference sample.</param>
/// <returns>The reference sample with the smallest absolute distance from the planar estimate.</returns>
private static byte PredictSingle(byte left, byte top, int topLeft)
{
int basis = top + left - topLeft;
int pLeft = Av1Math.AbsoluteDifference(basis, left);
int pTop = Av1Math.AbsoluteDifference(basis, top);
int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft);
// Ties prefer left, then top, matching AV1's normative Paeth selection order.
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft);
}
}

596
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs

@ -4,6 +4,8 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
@ -24,6 +26,21 @@ internal class Av1PredictionDecoder
/// </summary>
private const int MaxUpsampleSize = 16;
/// <summary>
/// The number of samples reserved for one prepared AV1 intra-prediction edge.
/// </summary>
private const int ReferenceBufferLength = (Av1Constants.MaxTransformSize * 2) + 32;
/// <summary>
/// The padded sample count required by the widest intra-edge SIMD loads.
/// </summary>
private const int EdgeScratchLength = 160;
/// <summary>
/// The number of high-bit-depth samples required by the reusable prediction workspace.
/// </summary>
public const int ScratchLength = Av1DirectionalIntraPredictor.ScratchLength + (2 * ReferenceBufferLength) + EdgeScratchLength;
/// <summary>
/// The sequence-level syntax that controls chroma sampling, bit depth, superblock size, and intra-edge filtering.
/// </summary>
@ -34,15 +51,22 @@ internal class Av1PredictionDecoder
/// </summary>
private readonly ObuFrameHeader frameHeader;
/// <summary>
/// The frame-owned workspace shared by directional and filter-intra predictors.
/// </summary>
private readonly Memory<short> predictorScratch;
/// <summary>
/// Initializes a new instance of the <see cref="Av1PredictionDecoder"/> class.
/// </summary>
/// <param name="sequenceHeader">The decoded sequence header for the current image.</param>
/// <param name="frameHeader">The decoded frame header for the current image.</param>
public Av1PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader)
/// <param name="predictorScratch">The reusable predictor workspace owned by the containing block decoder.</param>
public Av1PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Memory<short> predictorScratch)
{
this.sequenceHeader = sequenceHeader;
this.frameHeader = frameHeader;
this.predictorScratch = predictorScratch;
}
/// <summary>
@ -791,10 +815,15 @@ internal class Av1PredictionDecoder
{
int baseValue = 128 << (bitDepth - 8);
// The frame-owned allocation is sized in high-bit-depth samples. Reinterpreting it as T gives the byte
// path additional capacity while preserving the same sample offsets for the larger short representation.
Span<T> scratch = MemoryMarshal.Cast<short, T>(this.predictorScratch.Span);
Span<T> aboveData = scratch.Slice(Av1DirectionalIntraPredictor.ScratchLength, ReferenceBufferLength);
Span<T> leftData = scratch.Slice(Av1DirectionalIntraPredictor.ScratchLength + ReferenceBufferLength, ReferenceBufferLength);
Span<T> edgeScratch = scratch.Slice(Av1DirectionalIntraPredictor.ScratchLength + (2 * ReferenceBufferLength), EdgeScratchLength);
// Prefix storage is required because AV1 addresses the shared top-left sample at -1
// and writes upsampled edge samples as far back as -2.
Span<T> aboveData = stackalloc T[(Av1Constants.MaxTransformSize * 2) + 32];
Span<T> leftData = stackalloc T[(Av1Constants.MaxTransformSize * 2) + 32];
aboveData.Fill(T.CreateChecked(baseValue - 1));
leftData.Fill(T.CreateChecked(baseValue + 1));
Span<T> aboveRow = aboveData[16..];
@ -989,7 +1018,7 @@ internal class Av1PredictionDecoder
if (useFilterIntra)
{
FilterIntraPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode, bitDepth);
this.FilterIntraPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode, bitDepth);
return;
}
@ -1016,14 +1045,14 @@ internal class Av1PredictionDecoder
{
int strength = IntraEdgeFilterStrength(transformWidth, transformHeight, angle - 90, filterType);
int pixelCount = topPixelCount + ab_le + (needRight ? transformHeight : 0);
FilterIntraEdge(ref Unsafe.Subtract(ref aboveRow[0], ab_le), pixelCount, strength);
FilterIntraEdge(ref Unsafe.Subtract(ref aboveRow[0], ab_le), pixelCount, strength, edgeScratch);
}
if (needLeft && leftPixelCount > 0)
{
int strength = IntraEdgeFilterStrength(transformHeight, transformWidth, angle - 180, filterType);
int pixelCount = leftPixelCount + ab_le + (needBottom ? transformWidth : 0);
FilterIntraEdge(ref Unsafe.Subtract(ref leftColumn[0], ab_le), pixelCount, strength);
FilterIntraEdge(ref Unsafe.Subtract(ref leftColumn[0], ab_le), pixelCount, strength, edgeScratch);
}
}
@ -1032,7 +1061,7 @@ internal class Av1PredictionDecoder
{
int pixelCount = transformWidth + (needRight ? transformHeight : 0);
UpsampleIntraEdge(aboveRow, pixelCount, bitDepth);
UpsampleIntraEdge(aboveRow, pixelCount, bitDepth, edgeScratch);
}
upsampleLeft = UseIntraEdgeUpsample(transformHeight, transformWidth, angle - 180, filterType);
@ -1040,11 +1069,11 @@ internal class Av1PredictionDecoder
{
int pixelCount = transformHeight + (needBottom ? transformWidth : 0);
UpsampleIntraEdge(leftColumn, pixelCount, bitDepth);
UpsampleIntraEdge(leftColumn, pixelCount, bitDepth, edgeScratch);
}
}
DirectionalPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle, bitDepth);
this.DirectionalPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle);
return;
}
@ -1073,29 +1102,34 @@ internal class Av1PredictionDecoder
private static void DcPredictor<T>(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<T> destination, nuint destinationStride, Span<T> above, Span<T> left, int bitDepth)
where T : unmanaged
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
// DecodeCore is reachable only through byte and short overloads, so this type
// dispatch permits shared reference preparation without boxing or allocating.
if (typeof(T) == typeof(byte))
{
Av1PredictorFactory.DcPredictor(
Av1DcIntraPredictor.Predict(
hasLeft,
hasAbove,
transformSize,
MemoryMarshal.Cast<T, byte>(destination),
destinationStride,
(int)destinationStride,
MemoryMarshal.Cast<T, byte>(above),
MemoryMarshal.Cast<T, byte>(left));
MemoryMarshal.Cast<T, byte>(left),
width,
height);
}
else
{
Av1PredictorFactory.DcPredictor(
Av1DcIntraPredictor.Predict(
hasLeft,
hasAbove,
transformSize,
MemoryMarshal.Cast<T, short>(destination),
destinationStride,
(int)destinationStride,
MemoryMarshal.Cast<T, short>(above),
MemoryMarshal.Cast<T, short>(left),
width,
height,
bitDepth);
}
}
@ -1113,25 +1147,29 @@ internal class Av1PredictionDecoder
private static void GeneralPredictor<T>(Av1PredictionMode mode, Av1TransformSize transformSize, Span<T> destination, nuint destinationStride, Span<T> above, Span<T> left)
where T : unmanaged
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
Av1IntraPredictorBase predictor = Av1IntraPredictorBase.GetPredictor(mode);
if (typeof(T) == typeof(byte))
{
Av1PredictorFactory.GeneralPredictor(
mode,
transformSize,
predictor.Predict(
MemoryMarshal.Cast<T, byte>(destination),
destinationStride,
(int)destinationStride,
MemoryMarshal.Cast<T, byte>(above),
MemoryMarshal.Cast<T, byte>(left));
MemoryMarshal.Cast<T, byte>(left),
width,
height);
}
else
{
Av1PredictorFactory.GeneralPredictor(
mode,
transformSize,
predictor.Predict(
MemoryMarshal.Cast<T, short>(destination),
destinationStride,
(int)destinationStride,
MemoryMarshal.Cast<T, short>(above),
MemoryMarshal.Cast<T, short>(left));
MemoryMarshal.Cast<T, short>(left),
width,
height);
}
}
@ -1147,34 +1185,35 @@ internal class Av1PredictionDecoder
/// <param name="upsampleAbove">A value indicating whether the top edge was upsampled.</param>
/// <param name="upsampleLeft">A value indicating whether the left edge was upsampled.</param>
/// <param name="angle">The adjusted prediction angle in degrees.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
private static void DirectionalPredictor<T>(Span<T> destination, nuint destinationStride, Av1TransformSize transformSize, Span<T> above, Span<T> left, bool upsampleAbove, bool upsampleLeft, int angle, int bitDepth)
private void DirectionalPredictor<T>(Span<T> destination, nuint destinationStride, Av1TransformSize transformSize, Span<T> above, Span<T> left, bool upsampleAbove, bool upsampleLeft, int angle)
where T : unmanaged
{
if (typeof(T) == typeof(byte))
{
Av1PredictorFactory.DirectionalPredictor(
Span<byte> scratch = MemoryMarshal.AsBytes(this.predictorScratch.Span)[..Av1DirectionalIntraPredictor.ScratchLength];
Av1DirectionalIntraPredictor.Predict(
MemoryMarshal.Cast<T, byte>(destination),
destinationStride,
(int)destinationStride,
transformSize,
MemoryMarshal.Cast<T, byte>(above),
MemoryMarshal.Cast<T, byte>(left),
upsampleAbove,
upsampleLeft,
angle);
angle,
scratch);
}
else
{
Av1PredictorFactory.DirectionalPredictor(
Av1DirectionalIntraPredictor.Predict(
MemoryMarshal.Cast<T, short>(destination),
destinationStride,
(int)destinationStride,
transformSize,
MemoryMarshal.Cast<T, short>(above),
MemoryMarshal.Cast<T, short>(left),
upsampleAbove,
upsampleLeft,
angle,
bitDepth);
this.predictorScratch.Span);
}
}
@ -1189,29 +1228,36 @@ internal class Av1PredictionDecoder
/// <param name="left">The prepared left reference samples.</param>
/// <param name="mode">The filter intra mode whose coefficient set is applied.</param>
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
private static void FilterIntraPredictor<T>(Span<T> destination, nuint destinationStride, Av1TransformSize transformSize, Span<T> above, Span<T> left, Av1FilterIntraMode mode, int bitDepth)
private void FilterIntraPredictor<T>(Span<T> destination, nuint destinationStride, Av1TransformSize transformSize, Span<T> above, Span<T> left, Av1FilterIntraMode mode, int bitDepth)
where T : unmanaged
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
Av1FilterIntraPredictorBase predictor = Av1FilterIntraPredictorBase.GetPredictor(mode);
if (typeof(T) == typeof(byte))
{
Av1PredictorFactory.FilterIntraPredictor(
Span<byte> scratch = MemoryMarshal.AsBytes(this.predictorScratch.Span)[..Av1FilterIntraPredictorBase.ScratchLength];
predictor.Predict(
MemoryMarshal.Cast<T, byte>(destination),
destinationStride,
transformSize,
(int)destinationStride,
MemoryMarshal.Cast<T, byte>(above),
MemoryMarshal.Cast<T, byte>(left),
mode);
width,
height,
scratch);
}
else
{
Av1PredictorFactory.FilterIntraPredictor(
predictor.Predict(
MemoryMarshal.Cast<T, short>(destination),
destinationStride,
transformSize,
(int)destinationStride,
MemoryMarshal.Cast<T, short>(above),
MemoryMarshal.Cast<T, short>(left),
mode,
bitDepth);
width,
height,
bitDepth,
this.predictorScratch.Span[..Av1FilterIntraPredictorBase.ScratchLength]);
}
}
@ -1222,36 +1268,199 @@ internal class Av1PredictionDecoder
/// <param name="buffer">The edge buffer, including writable prefix storage at indices -2 and -1.</param>
/// <param name="count">The number of original edge samples to upsample.</param>
/// <param name="bitDepth">The number of bits used to clamp interpolated samples.</param>
private static void UpsampleIntraEdge<T>(Span<T> buffer, int count, int bitDepth)
/// <param name="scratch">The reusable padded source workspace.</param>
private static void UpsampleIntraEdge<T>(Span<T> buffer, int count, int bitDepth, Span<T> scratch)
where T : unmanaged, IBinaryInteger<T>
{
// TODO: Consider creating SIMD version
DebugGuard.MustBeLessThanOrEqualTo(count, MaxUpsampleSize, nameof(count));
Span<T> input = stackalloc T[MaxUpsampleSize + 3];
T beforeBuffer = Unsafe.Subtract(ref buffer[0], 1);
// DecodeBuildIntraPredictors is closed only over byte and short. Keeping that dispatch outside the
// kernels gives the JIT concrete vector element types and removes generic arithmetic from their loops.
if (typeof(T) == typeof(byte))
{
UpsampleIntraEdge(MemoryMarshal.Cast<T, byte>(buffer), count, MemoryMarshal.Cast<T, byte>(scratch));
}
else
{
UpsampleIntraEdge(MemoryMarshal.Cast<T, short>(buffer), count, bitDepth, MemoryMarshal.Cast<T, short>(scratch));
}
}
/// <summary>
/// Inserts half-sample positions into an 8-bit intra-prediction edge.
/// </summary>
/// <param name="buffer">The edge buffer, including writable prefix storage at indices -2 and -1.</param>
/// <param name="count">The number of original edge samples to upsample.</param>
/// <param name="scratch">The reusable padded source workspace.</param>
public static void UpsampleIntraEdge(Span<byte> buffer, int count, Span<byte> scratch)
{
ref byte bufferBase = ref MemoryMarshal.GetReference(buffer);
ref byte inputBase = ref MemoryMarshal.GetReference(scratch);
byte beforeBuffer = Unsafe.Subtract(ref bufferBase, 1);
byte finalSample = Unsafe.Add(ref bufferBase, count - 1);
// Vector loads intentionally extend past the logical edge. Initializing the complete load window with
// the final sample provides the AV1 endpoint extension and keeps every unaligned read inside scratch.
scratch[..32].Fill(finalSample);
inputBase = beforeBuffer;
Unsafe.Add(ref inputBase, 1) = beforeBuffer;
buffer[..count].CopyTo(scratch[2..]);
Unsafe.Subtract(ref bufferBase, 2) = beforeBuffer;
int i = 0;
if (Vector128.IsHardwareAccelerated)
{
int eightSamplesFromEnd = count - 8;
for (; i <= eightSamplesFromEnd; i += 8)
{
Vector128<byte> interpolated = InterpolateEightBytes(ref inputBase, i);
Vector128<byte> originals = Vector128.LoadUnsafe(ref inputBase, (nuint)(i + 2));
Vector128<byte> interleaved = Vector128_.UnpackLow(interpolated, originals);
interleaved.StoreUnsafe(ref Unsafe.Add(ref bufferBase, (2 * i) - 1));
}
// AV1 upsampled edges are normally multiples of four. A half-vector store handles that common
// remainder without overwriting the prepared extension beyond the logical output edge.
if (i <= count - 4)
{
Vector128<byte> interpolated = InterpolateEightBytes(ref inputBase, i);
Vector128<byte> originals = Vector128.LoadUnsafe(ref inputBase, (nuint)(i + 2));
Vector128<byte> interleaved = Vector128_.UnpackLow(interpolated, originals);
Unsafe.As<byte, ulong>(ref Unsafe.Add(ref bufferBase, (2 * i) - 1)) = interleaved.AsUInt64().GetElement(0);
i += 4;
}
}
// Duplicate both endpoints so the four-tap interpolation kernel can run at
// the edge without a separate boundary branch for its outer samples.
input[0] = beforeBuffer;
input[1] = beforeBuffer;
for (int i = 0; i < count; i++)
UpsampleIntraEdgeScalar(ref bufferBase, ref inputBase, i, count, 255);
}
/// <summary>
/// Inserts half-sample positions into a high-bit-depth intra-prediction edge.
/// </summary>
/// <param name="buffer">The edge buffer, including writable prefix storage at indices -2 and -1.</param>
/// <param name="count">The number of original edge samples to upsample.</param>
/// <param name="bitDepth">The number of bits used to clamp interpolated samples.</param>
/// <param name="scratch">The reusable padded source workspace.</param>
public static void UpsampleIntraEdge(Span<short> buffer, int count, int bitDepth, Span<short> scratch)
{
ref short bufferBase = ref MemoryMarshal.GetReference(buffer);
ref short inputBase = ref MemoryMarshal.GetReference(scratch);
short beforeBuffer = Unsafe.Subtract(ref bufferBase, 1);
short finalSample = Unsafe.Add(ref bufferBase, count - 1);
// The same padded layout is used for 10- and 12-bit edges. Arithmetic widens to Int32 before applying
// the four-tap kernel because the 12-bit intermediate exceeds the unsigned 16-bit range.
scratch[..32].Fill(finalSample);
inputBase = beforeBuffer;
Unsafe.Add(ref inputBase, 1) = beforeBuffer;
buffer[..count].CopyTo(scratch[2..]);
Unsafe.Subtract(ref bufferBase, 2) = beforeBuffer;
int maximum = (1 << bitDepth) - 1;
int i = 0;
if (Vector128.IsHardwareAccelerated)
{
input[i + 2] = buffer[i];
int eightSamplesFromEnd = count - 8;
for (; i <= eightSamplesFromEnd; i += 8)
{
Vector128<short> interpolated = InterpolateEightHighBitDepthSamples(ref inputBase, i, maximum);
Vector128<short> originals = Vector128.LoadUnsafe(ref inputBase, (nuint)(i + 2));
Vector128<short> interleavedLow = Vector128_.UnpackLow(interpolated, originals);
Vector128<short> interleavedHigh = Vector128_.UnpackHigh(interpolated, originals);
ref short destination = ref Unsafe.Add(ref bufferBase, (2 * i) - 1);
interleavedLow.StoreUnsafe(ref destination);
interleavedHigh.StoreUnsafe(ref destination, (nuint)Vector128<short>.Count);
}
if (i <= count - 4)
{
Vector128<short> interpolated = InterpolateEightHighBitDepthSamples(ref inputBase, i, maximum);
Vector128<short> originals = Vector128.LoadUnsafe(ref inputBase, (nuint)(i + 2));
Vector128<short> interleaved = Vector128_.UnpackLow(interpolated, originals);
interleaved.StoreUnsafe(ref Unsafe.Add(ref bufferBase, (2 * i) - 1));
i += 4;
}
}
input[count + 2] = buffer[count - 1];
UpsampleIntraEdgeScalar(ref bufferBase, ref inputBase, i, count, maximum);
}
/// <summary>
/// Calculates eight 8-bit half-sample values in parallel.
/// </summary>
/// <param name="input">The first padded input sample.</param>
/// <param name="offset">The first output sample index.</param>
/// <returns>The interpolated samples in the lower eight lanes.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> InterpolateEightBytes(ref byte input, int offset)
{
Vector128<byte> source0 = Vector128.LoadUnsafe(ref input, (nuint)offset);
Vector128<byte> source1 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 1));
Vector128<byte> source2 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 2));
Vector128<byte> source3 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 3));
(Vector128<ushort> source0Low, _) = Vector128.Widen(source0);
(Vector128<ushort> source1Low, _) = Vector128.Widen(source1);
(Vector128<ushort> source2Low, _) = Vector128.Widen(source2);
(Vector128<ushort> source3Low, _) = Vector128.Widen(source3);
Vector128<short> interpolation = (((source1Low + source2Low) * Vector128.Create((ushort)9)) - (source0Low + source3Low)).AsInt16();
interpolation = Vector128.Clamp((interpolation + Vector128.Create((short)8)) >> 4, Vector128<short>.Zero, Vector128.Create((short)255));
return Vector128.Narrow(interpolation.AsUInt16(), Vector128<ushort>.Zero);
}
/// <summary>
/// Calculates eight high-bit-depth half-sample values in parallel.
/// </summary>
/// <param name="input">The first padded input sample.</param>
/// <param name="offset">The first output sample index.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
/// <returns>The interpolated samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<short> InterpolateEightHighBitDepthSamples(ref short input, int offset, int maximum)
{
Vector128<short> source0 = Vector128.LoadUnsafe(ref input, (nuint)offset);
Vector128<short> source1 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 1));
Vector128<short> source2 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 2));
Vector128<short> source3 = Vector128.LoadUnsafe(ref input, (nuint)(offset + 3));
(Vector128<int> source0Low, Vector128<int> source0High) = Vector128.Widen(source0);
(Vector128<int> source1Low, Vector128<int> source1High) = Vector128.Widen(source1);
(Vector128<int> source2Low, Vector128<int> source2High) = Vector128.Widen(source2);
(Vector128<int> source3Low, Vector128<int> source3High) = Vector128.Widen(source3);
Vector128<int> coefficient = Vector128.Create(9);
Vector128<int> rounding = Vector128.Create(8);
Vector128<int> maximumVector = Vector128.Create(maximum);
Vector128<int> low = ((((source1Low + source2Low) * coefficient) - (source0Low + source3Low)) + rounding) >> 4;
Vector128<int> high = ((((source1High + source2High) * coefficient) - (source0High + source3High)) + rounding) >> 4;
low = Vector128.Clamp(low, Vector128<int>.Zero, maximumVector);
high = Vector128.Clamp(high, Vector128<int>.Zero, maximumVector);
return Vector128.Narrow(low, high);
}
Unsafe.Subtract(ref buffer[0], 2) = input[0];
ref T output = ref buffer[0];
for (int i = 0; i < count; i++)
/// <summary>
/// Inserts the scalar remainder of an intra-edge upsample operation.
/// </summary>
/// <typeparam name="T">The byte or 16-bit sample type.</typeparam>
/// <param name="buffer">The first original edge sample.</param>
/// <param name="input">The first padded input sample.</param>
/// <param name="start">The first sample not processed by SIMD.</param>
/// <param name="count">The number of original edge samples.</param>
/// <param name="maximum">The maximum reconstructed sample value.</param>
private static void UpsampleIntraEdgeScalar<T>(ref T buffer, ref T input, int start, int count, int maximum)
where T : unmanaged, IBinaryInteger<T>
{
for (int i = start; i < count; i++)
{
int s = -int.CreateChecked(input[i]) + (9 * int.CreateChecked(input[i + 1])) + (9 * int.CreateChecked(input[i + 2])) - int.CreateChecked(input[i + 3]);
s = Av1Math.Clamp((s + 8) >> 4, 0, (1 << bitDepth) - 1);
int interpolation = -int.CreateChecked(Unsafe.Add(ref input, i))
+ (9 * int.CreateChecked(Unsafe.Add(ref input, i + 1)))
+ (9 * int.CreateChecked(Unsafe.Add(ref input, i + 2)))
- int.CreateChecked(Unsafe.Add(ref input, i + 3));
interpolation = Av1Math.Clamp((interpolation + 8) >> 4, 0, maximum);
// The AOM edge buffer reserves prefix storage for the samples at indices -2 and -1.
Unsafe.Add(ref output, (2 * i) - 1) = T.CreateChecked(s);
Unsafe.Add(ref output, 2 * i) = input[i + 2];
Unsafe.Add(ref buffer, (2 * i) - 1) = T.CreateChecked(interpolation);
Unsafe.Add(ref buffer, 2 * i) = Unsafe.Add(ref input, i + 2);
}
}
@ -1283,38 +1492,261 @@ internal class Av1PredictionDecoder
/// <param name="buffer">A reference to the first edge sample to filter.</param>
/// <param name="count">The number of edge samples.</param>
/// <param name="strength">The AV1 filter-strength index from zero through three.</param>
/// <param name="scratch">The reusable padded source workspace.</param>
/// <remarks>Corresponds to <c>svt_av1_filter_intra_edge_c</c> in SVT-AV1.</remarks>
private static void FilterIntraEdge<T>(ref T buffer, int count, int strength)
private static void FilterIntraEdge<T>(ref T buffer, int count, int strength, Span<T> scratch)
where T : unmanaged, IBinaryInteger<T>
{
// TODO: Consider creating SIMD version
if (strength == 0)
{
return;
}
int[][] kernel = [
[0, 4, 8, 4, 0], [0, 5, 6, 5, 0], [2, 4, 4, 4, 2]
];
int filt = strength - 1;
Span<T> edge = stackalloc T[129];
// As with edge upsampling, closing the kernel over a concrete sample type keeps vector arithmetic
// outside the generic decoder while the valid strength-zero no-op remains at the owning boundary.
if (typeof(T) == typeof(byte))
{
FilterIntraEdge(ref Unsafe.As<T, byte>(ref buffer), count, strength, MemoryMarshal.Cast<T, byte>(scratch));
}
else
{
FilterIntraEdge(ref Unsafe.As<T, short>(ref buffer), count, strength, MemoryMarshal.Cast<T, short>(scratch));
}
}
// Filtering must read the original edge throughout the convolution, so retain
// a scratch copy rather than feeding earlier filtered samples into later outputs.
MemoryMarshal.CreateSpan(ref buffer, count).CopyTo(edge);
for (int i = 1; i < count; i++)
/// <summary>
/// Applies an AV1 intra-edge smoothing kernel to 8-bit samples.
/// </summary>
/// <param name="buffer">A reference to the first edge sample to filter.</param>
/// <param name="count">The number of edge samples.</param>
/// <param name="strength">The AV1 filter-strength index from one through three.</param>
/// <param name="scratch">The reusable padded source workspace.</param>
public static void FilterIntraEdge(ref byte buffer, int count, int strength, Span<byte> scratch)
{
byte finalSample = Unsafe.Add(ref buffer, count - 1);
// The original edge is retained because each convolution window must observe unfiltered neighbors.
// Padding both endpoints also makes every complete vector use the same contiguous load pattern.
scratch[..EdgeScratchLength].Fill(finalSample);
scratch[0] = buffer;
MemoryMarshal.CreateReadOnlySpan(ref buffer, count).CopyTo(scratch[1..]);
ref byte edge = ref MemoryMarshal.GetReference(scratch);
int outputCount = count - 1;
int processed = 0;
if (Vector128.IsHardwareAccelerated)
{
int s = 0;
for (int j = 0; j < 5; j++)
int eightSamplesFromEnd = outputCount - 8;
switch (strength)
{
int k = i - 2 + j;
k = (k < 0) ? 0 : k;
k = (k > count - 1) ? count - 1 : k;
s += int.CreateChecked(edge[k]) * kernel[filt][j];
case 1:
for (; processed <= eightSamplesFromEnd; processed += 8)
{
Vector128<ushort> source0 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)));
Vector128<ushort> source1 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)));
Vector128<ushort> source2 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)));
Vector128<byte> result = Vector128.Narrow(FilterEdgeStrength1(source0, source1, source2), Vector128<ushort>.Zero);
Unsafe.As<byte, ulong>(ref Unsafe.Add(ref buffer, processed + 1)) = result.AsUInt64().GetElement(0);
}
break;
case 2:
for (; processed <= eightSamplesFromEnd; processed += 8)
{
Vector128<ushort> source0 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)));
Vector128<ushort> source1 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)));
Vector128<ushort> source2 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)));
Vector128<byte> result = Vector128.Narrow(FilterEdgeStrength2(source0, source1, source2), Vector128<ushort>.Zero);
Unsafe.As<byte, ulong>(ref Unsafe.Add(ref buffer, processed + 1)) = result.AsUInt64().GetElement(0);
}
break;
default:
for (; processed <= eightSamplesFromEnd; processed += 8)
{
Vector128<ushort> source0 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)processed));
Vector128<ushort> source1 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)));
Vector128<ushort> source2 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)));
Vector128<ushort> source3 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)));
Vector128<ushort> source4 = WidenLower(Vector128.LoadUnsafe(ref edge, (nuint)(processed + 4)));
Vector128<byte> result = Vector128.Narrow(FilterEdgeStrength3(source0, source1, source2, source3, source4), Vector128<ushort>.Zero);
Unsafe.As<byte, ulong>(ref Unsafe.Add(ref buffer, processed + 1)) = result.AsUInt64().GetElement(0);
}
break;
}
}
FilterIntraEdgeScalar(ref buffer, ref edge, processed, outputCount, strength);
}
s = (s + 8) >> 4;
Unsafe.Add(ref buffer, i) = T.CreateChecked(s);
/// <summary>
/// Applies an AV1 intra-edge smoothing kernel to high-bit-depth samples.
/// </summary>
/// <param name="buffer">A reference to the first edge sample to filter.</param>
/// <param name="count">The number of edge samples.</param>
/// <param name="strength">The AV1 filter-strength index from one through three.</param>
/// <param name="scratch">The reusable padded source workspace.</param>
public static void FilterIntraEdge(ref short buffer, int count, int strength, Span<short> scratch)
{
short finalSample = Unsafe.Add(ref buffer, count - 1);
scratch[..EdgeScratchLength].Fill(finalSample);
scratch[0] = buffer;
MemoryMarshal.CreateReadOnlySpan(ref buffer, count).CopyTo(scratch[1..]);
ref short edge = ref MemoryMarshal.GetReference(scratch);
int outputCount = count - 1;
int processed = 0;
// The largest 12-bit weighted sum is 65520; the greatest rounding bias raises that only to 65528.
// Unsigned 16-bit lanes therefore preserve every normative strength without widening to 32-bit vectors.
if (Vector128.IsHardwareAccelerated)
{
int eightSamplesFromEnd = outputCount - Vector128<short>.Count;
switch (strength)
{
case 1:
for (; processed <= eightSamplesFromEnd; processed += Vector128<short>.Count)
{
Vector128<ushort> source0 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)).AsUInt16();
Vector128<ushort> source1 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)).AsUInt16();
Vector128<ushort> source2 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)).AsUInt16();
FilterEdgeStrength1(source0, source1, source2).AsInt16().StoreUnsafe(ref buffer, (nuint)(processed + 1));
}
break;
case 2:
for (; processed <= eightSamplesFromEnd; processed += Vector128<short>.Count)
{
Vector128<ushort> source0 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)).AsUInt16();
Vector128<ushort> source1 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)).AsUInt16();
Vector128<ushort> source2 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)).AsUInt16();
FilterEdgeStrength2(source0, source1, source2).AsInt16().StoreUnsafe(ref buffer, (nuint)(processed + 1));
}
break;
default:
for (; processed <= eightSamplesFromEnd; processed += Vector128<short>.Count)
{
Vector128<ushort> source0 = Vector128.LoadUnsafe(ref edge, (nuint)processed).AsUInt16();
Vector128<ushort> source1 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 1)).AsUInt16();
Vector128<ushort> source2 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 2)).AsUInt16();
Vector128<ushort> source3 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 3)).AsUInt16();
Vector128<ushort> source4 = Vector128.LoadUnsafe(ref edge, (nuint)(processed + 4)).AsUInt16();
FilterEdgeStrength3(source0, source1, source2, source3, source4).AsInt16().StoreUnsafe(ref buffer, (nuint)(processed + 1));
}
break;
}
}
FilterIntraEdgeScalar(ref buffer, ref edge, processed, outputCount, strength);
}
/// <summary>
/// Widens the lower eight lanes of a byte vector for edge-filter arithmetic.
/// </summary>
/// <param name="source">The packed source samples.</param>
/// <returns>The widened samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<ushort> WidenLower(Vector128<byte> source)
{
(Vector128<ushort> lower, _) = Vector128.Widen(source);
return lower;
}
/// <summary>
/// Applies the strength-one three-tap edge filter to eight samples.
/// </summary>
/// <param name="source0">The preceding samples.</param>
/// <param name="source1">The centered samples.</param>
/// <param name="source2">The following samples.</param>
/// <returns>The filtered samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<ushort> FilterEdgeStrength1(Vector128<ushort> source0, Vector128<ushort> source1, Vector128<ushort> source2)
=> (source0 + (source1 << 1) + source2 + Vector128.Create((ushort)2)) >> 2;
/// <summary>
/// Applies the strength-two three-tap edge filter to eight samples.
/// </summary>
/// <param name="source0">The preceding samples.</param>
/// <param name="source1">The centered samples.</param>
/// <param name="source2">The following samples.</param>
/// <returns>The filtered samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<ushort> FilterEdgeStrength2(Vector128<ushort> source0, Vector128<ushort> source1, Vector128<ushort> source2)
=> (((source0 + source2) * Vector128.Create((ushort)5)) + (source1 * Vector128.Create((ushort)6)) + Vector128.Create((ushort)8)) >> 4;
/// <summary>
/// Applies the strength-three five-tap edge filter to eight samples.
/// </summary>
/// <param name="source0">The samples two positions before each output.</param>
/// <param name="source1">The preceding samples.</param>
/// <param name="source2">The centered samples.</param>
/// <param name="source3">The following samples.</param>
/// <param name="source4">The samples two positions after each output.</param>
/// <returns>The filtered samples.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<ushort> FilterEdgeStrength3(
Vector128<ushort> source0,
Vector128<ushort> source1,
Vector128<ushort> source2,
Vector128<ushort> source3,
Vector128<ushort> source4)
=> (source0 + ((source1 + source2 + source3) << 1) + source4 + Vector128.Create((ushort)4)) >> 3;
/// <summary>
/// Applies an AV1 edge filter to samples not consumed by the vector loop.
/// </summary>
/// <typeparam name="T">The byte or 16-bit sample type.</typeparam>
/// <param name="buffer">The first destination sample.</param>
/// <param name="edge">The first padded source sample.</param>
/// <param name="start">The first output index not processed by SIMD.</param>
/// <param name="count">The number of filtered outputs following the preserved first sample.</param>
/// <param name="strength">The AV1 filter-strength index.</param>
private static void FilterIntraEdgeScalar<T>(ref T buffer, ref T edge, int start, int count, int strength)
where T : unmanaged, IBinaryInteger<T>
{
switch (strength)
{
case 1:
for (int i = start; i < count; i++)
{
int sourceOffset = i + 1;
int value = int.CreateChecked(Unsafe.Add(ref edge, sourceOffset))
+ (2 * int.CreateChecked(Unsafe.Add(ref edge, sourceOffset + 1)))
+ int.CreateChecked(Unsafe.Add(ref edge, sourceOffset + 2));
Unsafe.Add(ref buffer, i + 1) = T.CreateChecked((value + 2) >> 2);
}
break;
case 2:
for (int i = start; i < count; i++)
{
int sourceOffset = i + 1;
int value = (5 * int.CreateChecked(Unsafe.Add(ref edge, sourceOffset)))
+ (6 * int.CreateChecked(Unsafe.Add(ref edge, sourceOffset + 1)))
+ (5 * int.CreateChecked(Unsafe.Add(ref edge, sourceOffset + 2)));
Unsafe.Add(ref buffer, i + 1) = T.CreateChecked((value + 8) >> 4);
}
break;
default:
for (int i = start; i < count; i++)
{
int value = int.CreateChecked(Unsafe.Add(ref edge, i))
+ (2 * (int.CreateChecked(Unsafe.Add(ref edge, i + 1))
+ int.CreateChecked(Unsafe.Add(ref edge, i + 2))
+ int.CreateChecked(Unsafe.Add(ref edge, i + 3))))
+ int.CreateChecked(Unsafe.Add(ref edge, i + 4));
Unsafe.Add(ref buffer, i + 1) = T.CreateChecked((value + 4) >> 3);
}
break;
}
}

275
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs

@ -1,275 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Selects the scalar 8-bit or high-bit-depth AV1 intra predictor for a decoded prediction mode.
/// </summary>
internal class Av1PredictorFactory
{
/// <summary>
/// The Q8 directional derivatives indexed by acute angle in degrees; zero entries represent angles AV1 does not signal.
/// </summary>
private static readonly int[] DirectionalIntraDerivative = [
// More evenly spread out angles and limited to 10-bit
// Values that are 0 will never be used
// Approx angle
0, 0, 0, // 0
1023, 0, 0, // 3, ...
547, 0, 0, // 6, ...
372, 0, 0, 0, 0, // 9, ...
273, 0, 0, // 14, ...
215, 0, 0, // 17, ...
178, 0, 0, // 20, ...
151, 0, 0, // 23, ... (113 & 203 are base angles)
132, 0, 0, // 26, ...
116, 0, 0, // 29, ...
102, 0, 0, 0, // 32, ...
90, 0, 0, // 36, ...
80, 0, 0, // 39, ...
71, 0, 0, // 42, ...
64, 0, 0, // 45, ... (45 & 135 are base angles)
57, 0, 0, // 48, ...
51, 0, 0, // 51, ...
45, 0, 0, 0, // 54, ...
40, 0, 0, // 58, ...
35, 0, 0, // 61, ...
31, 0, 0, // 64, ...
27, 0, 0, // 67, ... (67 & 157 are base angles)
23, 0, 0, // 70, ...
19, 0, 0, // 73, ...
15, 0, 0, 0, 0, // 76, ...
11, 0, 0, // 81, ...
7, 0, 0, // 84, ...
3, 0, 0, // 87, ...
];
/// <summary>
/// Predicts an 8-bit block from the average of whichever top and left neighbor edges are available.
/// </summary>
/// <param name="hasLeft">Whether the left neighboring column is available.</param>
/// <param name="hasAbove">Whether the top neighboring row is available.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
public static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
{
if (hasLeft)
{
if (hasAbove)
{
Av1DcPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
else
{
Av1DcLeftPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
}
else
{
if (hasAbove)
{
Av1DcTopPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
else
{
Av1DcFillPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
}
}
}
/// <summary>
/// Predicts a high-bit-depth block from the average of whichever top and left neighbor edges are available.
/// </summary>
/// <param name="hasLeft">Whether the left neighboring column is available.</param>
/// <param name="hasAbove">Whether the top neighboring row is available.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
/// <param name="bitDepth">The coded sample bit depth used to select the midpoint when no edge is available.</param>
public static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn, int bitDepth)
=> Av1HighBitDepthPredictor.DcPredictor(hasLeft, hasAbove, transformSize, destination, destinationStride, aboveRow, leftColumn, bitDepth);
/// <summary>
/// Predicts an 8-bit block by projecting reference-edge samples along a directional angle.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="aboveRow">The top and top-right reference samples.</param>
/// <param name="leftColumn">The left and bottom-left reference samples.</param>
/// <param name="upsampleAbove">Whether the top reference edge is stored at half-sample intervals.</param>
/// <param name="upsampleLeft">Whether the left reference edge is stored at half-sample intervals.</param>
/// <param name="angle">The prediction angle in degrees from 1 through 269.</param>
/// <remarks>SVT-AV1: <c>svt_aom_highbd_dr_predictor</c>.</remarks>
public static void DirectionalPredictor(Span<byte> destination, nuint stride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle)
{
int dx = GetDeltaX(angle);
int dy = GetDeltaY(angle);
int bw = transformSize.GetWidth();
int bh = transformSize.GetHeight();
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle));
// The three open quadrants select which reference edge or edge pair the projected ray intersects.
// Exact 90- and 180-degree modes reduce to copying the top row or left column without interpolation.
if (angle is > 0 and < 90)
{
Av1DirectionalZone1Predictor.PredictScalar(transformSize, destination, stride, aboveRow, upsampleAbove, dx);
}
else if (angle is > 90 and < 180)
{
Av1DirectionalZone2Predictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn, upsampleAbove, upsampleLeft, dx, dy);
}
else if (angle is > 180 and < 270)
{
Av1DirectionalZone3Predictor.PredictScalar(transformSize, destination, stride, leftColumn, upsampleLeft, dx, dy);
}
else if (angle == 90)
{
Av1VerticalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
}
else if (angle == 180)
{
Av1HorizontalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn);
}
}
/// <summary>
/// Predicts a high-bit-depth block by projecting reference-edge samples along a directional angle.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="aboveRow">The top and top-right reference samples.</param>
/// <param name="leftColumn">The left and bottom-left reference samples.</param>
/// <param name="upsampleAbove">Whether the top reference edge is stored at half-sample intervals.</param>
/// <param name="upsampleLeft">Whether the left reference edge is stored at half-sample intervals.</param>
/// <param name="angle">The prediction angle in degrees from 1 through 269.</param>
/// <param name="bitDepth">The coded sample bit depth used to clamp interpolated values.</param>
public static void DirectionalPredictor(Span<short> destination, nuint stride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle, int bitDepth)
=> Av1HighBitDepthPredictor.DirectionalPredictor(destination, stride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle, bitDepth);
/// <summary>
/// Predicts an 8-bit block using the selected AV1 filter-intra kernel.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
/// <param name="filterIntraMode">The filter-intra coefficient set.</param>
public static void FilterIntraPredictor(Span<byte> destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, Av1FilterIntraMode filterIntraMode)
=> Av1FilterIntraPredictor.Predict(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode);
/// <summary>
/// Predicts a high-bit-depth block using the selected AV1 filter-intra kernel.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
/// <param name="filterIntraMode">The filter-intra coefficient set.</param>
/// <param name="bitDepth">The coded sample bit depth used to clamp filtered values.</param>
public static void FilterIntraPredictor(Span<short> destination, nuint destinationStride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, Av1FilterIntraMode filterIntraMode, int bitDepth)
=> Av1HighBitDepthPredictor.FilterIntraPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode, bitDepth);
/// <summary>
/// Selects an 8-bit horizontal, vertical, Paeth, or smooth predictor for a non-directional mode.
/// </summary>
/// <param name="mode">The non-directional prediction mode.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
public static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
{
switch (mode)
{
case Av1PredictionMode.Horizontal:
Av1HorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Vertical:
Av1VerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Paeth:
Av1PaethPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.Smooth:
Av1SmoothPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.SmoothHorizontal:
Av1SmoothHorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
case Av1PredictionMode.SmoothVertical:
Av1SmoothVerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn);
break;
}
}
/// <summary>
/// Selects a high-bit-depth horizontal, vertical, Paeth, or smooth predictor for a non-directional mode.
/// </summary>
/// <param name="mode">The non-directional prediction mode.</param>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
/// <param name="aboveRow">The top neighboring samples.</param>
/// <param name="leftColumn">The left neighboring samples.</param>
public static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn)
=> Av1HighBitDepthPredictor.GeneralPredictor(mode, transformSize, destination, destinationStride, aboveRow, leftColumn);
/// <summary>
/// Gets the Q8 vertical displacement per unit horizontal displacement for a directional angle.
/// </summary>
/// <param name="angle">The prediction angle in degrees.</param>
/// <returns>The Q8 vertical derivative, or one when the selected directional zone does not consume it.</returns>
public static int GetDeltaY(int angle)
{
if (angle is > 90 and < 180)
{
return DirectionalIntraDerivative[angle - 90];
}
else if (angle is > 180 and < 270)
{
return DirectionalIntraDerivative[270 - angle];
}
else
{
// Zones one and the exact horizontal/vertical modes never consume dy; one avoids a zero placeholder.
return 1;
}
}
/// <summary>
/// Gets the Q8 horizontal displacement per unit vertical displacement for a directional angle.
/// </summary>
/// <param name="angle">The prediction angle in degrees.</param>
/// <returns>The Q8 horizontal derivative, or one when the selected directional zone does not consume it.</returns>
public static int GetDeltaX(int angle)
{
if (angle is > 0 and < 90)
{
return DirectionalIntraDerivative[angle];
}
else if (angle is > 90 and < 180)
{
return DirectionalIntraDerivative[180 - angle];
}
else
{
// Zone three and the exact horizontal/vertical modes never consume dx; one avoids a zero placeholder.
return 1;
}
}
}

89
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs

@ -1,89 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by smoothly blending each left neighbor toward the top-right reference sample.
/// </summary>
internal readonly struct Av1SmoothHorizontalPredictor : IAv1Predictor
{
/// <summary>
/// The number of interpolation weights and samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of left samples consumed and destination rows written.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothHorizontalPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1SmoothHorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothHorizontalPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1SmoothHorizontalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block using horizontal smooth interpolation.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples whose final value supplies the right endpoint.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
/// <remarks>SVT-AV1: <c>highbd_smooth_h_predictor</c>.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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];
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockWidth];
int log2Scale = Av1SmoothPredictor.WeightLog2Scale;
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
for (nuint c = 0; c < this.blockWidth; ++c)
{
int columnWeight = Unsafe.Add(ref weights, c);
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
// The Q8 weight decreases toward the right edge, shifting influence from left to top-right.
int thisPredition = Unsafe.Add(ref leftRef, r) * columnWeight;
thisPredition += rightPrediction * (scale - columnWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

135
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs

@ -1,135 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by blending top-to-bottom and left-to-right smooth interpolation surfaces.
/// </summary>
internal readonly struct Av1SmoothPredictor : IAv1Predictor
{
// Weights are quadratic from '1' to '1 / BlockSize', scaled by
// 2^sm_weight_log2_scale.
/// <summary>
/// The number of fractional bits in the normative smooth-prediction weights.
/// </summary>
internal static readonly int WeightLog2Scale = 8;
/// <summary>
/// The concatenated smooth-weight sequences, addressed by using the block dimension as the sequence offset.
/// </summary>
internal static readonly int[] Weights = [
// Unused, because we always offset by bs, which is at least 2.
0, 0,
// bs = 2
255, 128,
// bs = 4
255, 149, 85, 64,
// bs = 8
255, 197, 146, 105, 73, 50, 37, 32,
// bs = 16
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16,
// bs = 32
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74,
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8,
// bs = 64
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156,
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69,
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15,
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4,
];
/// <summary>
/// The number of top samples consumed and samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of left samples consumed and destination rows written.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1SmoothPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1SmoothPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block by combining horizontal and vertical smooth interpolation.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples.</param>
/// <param name="left">The left neighboring samples.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
/// <remarks>SVT-AV1: <c>highbd_smooth_predictor</c>.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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];
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
ref int heightWeights = ref Weights[(int)this.blockHeight];
ref int widthWeights = ref Weights[(int)this.blockWidth];
// The two independent interpolation surfaces each use Q8 weights, so their combined sum has one extra scale bit.
int log2Scale = 1 + WeightLog2Scale;
int scale = 1 << WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
int rowWeight = Unsafe.Add(ref heightWeights, r);
Guard.MustBeGreaterThanOrEqualTo(scale, rowWeight, nameof(scale));
for (nuint c = 0; c < this.blockWidth; ++c)
{
int columnWeight = Unsafe.Add(ref widthWeights, c);
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale));
// Blend top toward bottom-left and left toward top-right, then normalize their combined Q8 contributions.
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
thisPredition += belowPrediction * (scale - rowWeight);
thisPredition += Unsafe.Add(ref leftRef, r) * columnWeight;
thisPredition += rightPrediction * (scale - columnWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

87
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs

@ -1,87 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by smoothly blending each top neighbor toward the bottom-left reference sample.
/// </summary>
internal readonly struct Av1SmoothVerticalPredictor : IAv1Predictor
{
/// <summary>
/// The number of top samples consumed and samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of interpolation weights, left samples consumed, and destination rows written.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothVerticalPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1SmoothVerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1SmoothVerticalPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1SmoothVerticalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block using vertical smooth interpolation.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples.</param>
/// <param name="left">The left neighboring samples whose final value supplies the bottom endpoint.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
/// <remarks>SVT-AV1: <c>highbd_smooth_v_predictor</c>.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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];
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockHeight];
int log2Scale = Av1SmoothPredictor.WeightLog2Scale;
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
for (nuint r = 0; r < this.blockHeight; ++r)
{
int rowWeight = Unsafe.Add(ref weights, r);
for (nuint c = 0; c < this.blockWidth; ++c)
{
// The Q8 weight decreases toward the bottom edge, shifting influence from top to bottom-left.
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight;
thisPredition += belowPrediction * (scale - rowWeight);
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale);
}
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

72
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs

@ -1,72 +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;
/// <summary>
/// Predicts an 8-bit AV1 block by copying the top neighboring samples into every destination row.
/// </summary>
internal readonly struct Av1VerticalPredictor : IAv1Predictor
{
/// <summary>
/// The number of top samples consumed and samples written to each destination row.
/// </summary>
private readonly nuint blockWidth;
/// <summary>
/// The number of destination rows.
/// </summary>
private readonly nuint blockHeight;
/// <summary>
/// Initializes a new instance of the <see cref="Av1VerticalPredictor"/> struct for explicit block dimensions.
/// </summary>
/// <param name="blockSize">The predicted block dimensions in samples.</param>
public Av1VerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Av1VerticalPredictor"/> struct for a transform size.
/// </summary>
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
public Av1VerticalPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
/// <summary>
/// Predicts a transform block by extending its top edge vertically.
/// </summary>
/// <param name="transformSize">The predicted block dimensions.</param>
/// <param name="destination">The destination block.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The top neighboring samples.</param>
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
=> new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <inheritdoc/>
/// <remarks>SVT-AV1: <c>highbd_v_predictor</c>.</remarks>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
{
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 aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
uint width = (uint)this.blockWidth;
for (nuint r = 0; r < this.blockHeight; ++r)
{
Unsafe.CopyBlock(ref destinationRef, ref aboveRef, width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}

19
src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Defines scalar reconstruction of an 8-bit AV1 intra-prediction block from its neighboring samples.
/// </summary>
internal interface IAv1Predictor
{
/// <summary>
/// Writes the predicted block using scalar 8-bit arithmetic.
/// </summary>
/// <param name="destination">The destination block, starting at its top-left sample.</param>
/// <param name="stride">The distance, in samples, between destination rows.</param>
/// <param name="above">The neighboring samples immediately above the block.</param>
/// <param name="left">The neighboring samples immediately left of the block.</param>
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left);
}

18
src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs

@ -52,6 +52,16 @@ internal sealed class Av1BlockDecoder : IDisposable
/// </summary>
private readonly IMemoryOwner<int> transformWorkspaceOwner;
/// <summary>
/// Owns the reusable directional and filter-intra prediction workspace.
/// </summary>
private readonly IMemoryOwner<short> predictionScratchOwner;
/// <summary>
/// Reconstructs intra-predicted blocks using the frame-owned prediction workspace.
/// </summary>
private readonly Av1PredictionDecoder predictionDecoder;
/// <summary>
/// Indicates whether transform traversal must also populate loop-filter parameters.
/// </summary>
@ -97,6 +107,8 @@ internal sealed class Av1BlockDecoder : IDisposable
this.inverseQuantizationOwner = this.frameBuffer.MemoryAllocator.Allocate<int>(inverseQuantizationSize);
this.transformWorkspaceOwner = this.frameBuffer.MemoryAllocator.Allocate<int>(Av1TransformWorkspace.MaximumLength);
this.predictionScratchOwner = this.frameBuffer.MemoryAllocator.Allocate<short>(Av1PredictionDecoder.ScratchLength);
this.predictionDecoder = new(sequenceHeader, frameHeader, this.predictionScratchOwner.Memory);
this.isLoopFilterEnabled = frameHeader.LoopFilterParameters.FilterLevel[0] != 0 ||
frameHeader.LoopFilterParameters.FilterLevel[1] != 0;
@ -114,6 +126,7 @@ internal sealed class Av1BlockDecoder : IDisposable
/// </summary>
public void Dispose()
{
this.predictionScratchOwner.Dispose();
this.transformWorkspaceOwner.Dispose();
this.inverseQuantizationOwner.Dispose();
}
@ -184,7 +197,6 @@ internal sealed class Av1BlockDecoder : IDisposable
: modeInfo.GetTransformUnitCount(Av1Plane.U);
bool highBitDepth = this.frameBuffer.BytesPerSample == 2;
Av1PredictionDecoder predictionDecoder = new(this.sequenceHeader, this.frameHeader);
for (int plane = 0; plane < colorConfig.PlaneCount; plane++)
{
int subX = (plane > 0) && colorConfig.SubSamplingX ? 1 : 0;
@ -277,7 +289,7 @@ internal sealed class Av1BlockDecoder : IDisposable
// predicts its samples before any coded residual is added.
if (highBitDepth)
{
predictionDecoder.Decode(
this.predictionDecoder.Decode(
partitionInfo,
(Av1Plane)plane,
transformSize,
@ -290,7 +302,7 @@ internal sealed class Av1BlockDecoder : IDisposable
}
else
{
predictionDecoder.Decode(
this.predictionDecoder.Decode(
partitionInfo,
(Av1Plane)plane,
transformSize,

1210
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

File diff suppressed because it is too large
Loading…
Cancel
Save