Browse Source

Document and correct AV1 prediction

pull/2633/head
James Jackson-South 1 week ago
parent
commit
6095963f09
  1. 28
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs
  2. 28
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs
  3. 28
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
  4. 28
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs
  5. 41
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
  6. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
  7. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
  8. 49
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.cs
  9. 187
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HighBitDepthPredictor.cs
  10. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
  11. 26
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs
  12. 36
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
  13. 82
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs
  14. 131
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  15. 32
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs
  16. 33
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
  17. 46
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
  18. 32
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
  19. 31
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
  20. 12
      src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs
  21. 24
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

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

@ -6,28 +6,56 @@ 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 class 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"/> class 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"/> class 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));

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

@ -6,26 +6,53 @@ 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 class 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"/> class 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"/> class 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;
@ -39,6 +66,7 @@ internal class Av1DcLeftPredictor : IAv1Predictor
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++)
{

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

@ -6,26 +6,53 @@ 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 class 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"/> class 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"/> class 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;
@ -48,6 +75,7 @@ internal class Av1DcPredictor : IAv1Predictor
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++)
{

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

@ -6,26 +6,53 @@ 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 class 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"/> class 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"/> class 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;
@ -39,6 +66,7 @@ internal class Av1DcTopPredictor : IAv1Predictor
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++)
{

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

@ -6,29 +6,66 @@ 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 class 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"/> class 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"/> class 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>
/// SVT: svt_av1_dr_prediction_z1_c
/// 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));
@ -42,6 +79,8 @@ internal class Av1DirectionalZone1Predictor
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)

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

@ -8,29 +8,72 @@ 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 class 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"/> class 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"/> class 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>
/// SVT: svt_av1_dr_prediction_z1_c
/// 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));
@ -54,6 +97,8 @@ internal class Av1DirectionalZone2Predictor
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;

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

@ -7,29 +7,68 @@ 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 class 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"/> class 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"/> class 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();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> left, bool upsampleAbove, int dx, int dy)
=> new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleAbove, dx, dy);
/// <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>
/// SVT: svt_av1_dr_prediction_z3_c
/// 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));
@ -47,6 +86,8 @@ internal class Av1DirectionalZone3Predictor
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;

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

@ -8,17 +8,47 @@ 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;
// AV1 7.11.2.3 defines five sets of eight filters over the same seven
// already-reconstructed neighbors. The omitted eighth libaom tap is zero.
internal static readonly sbyte[] Taps =
/// <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
@ -72,7 +102,16 @@ internal static class Av1FilterIntraPredictor
-7, 0, 0, 1, 12, 1, 9,
];
internal static void Predict(
/// <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,
@ -95,6 +134,8 @@ internal static class Av1FilterIntraPredictor
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++)

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

@ -10,15 +10,48 @@ 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;
internal static void DcPredictor(
/// <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,
@ -57,10 +90,21 @@ internal static class Av1HighBitDepthPredictor
? (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);
}
internal static void GeneralPredictor(
/// <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,
@ -94,7 +138,19 @@ internal static class Av1HighBitDepthPredictor
}
}
internal static void DirectionalPredictor(
/// <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,
@ -111,6 +167,8 @@ internal static class Av1HighBitDepthPredictor
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);
@ -133,7 +191,17 @@ internal static class Av1HighBitDepthPredictor
}
}
internal static void FilterIntraPredictor(
/// <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,
@ -158,6 +226,8 @@ internal static class Av1HighBitDepthPredictor
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++)
@ -208,6 +278,14 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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++)
@ -216,6 +294,14 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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++)
@ -224,6 +310,15 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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);
@ -238,6 +333,8 @@ internal static class Av1HighBitDepthPredictor
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;
@ -245,6 +342,15 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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];
@ -254,6 +360,8 @@ internal static class Av1HighBitDepthPredictor
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];
@ -268,6 +376,15 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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];
@ -286,6 +403,15 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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];
@ -304,6 +430,17 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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;
@ -317,6 +454,9 @@ internal static class Av1HighBitDepthPredictor
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++)
@ -338,6 +478,20 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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;
@ -359,6 +513,9 @@ internal static class Av1HighBitDepthPredictor
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;
@ -378,6 +535,18 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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;
@ -391,6 +560,8 @@ internal static class Av1HighBitDepthPredictor
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++)
@ -413,6 +584,14 @@ internal static class Av1HighBitDepthPredictor
}
}
/// <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++)

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

@ -6,29 +6,54 @@ 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 class 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"/> class 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"/> class 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);
/// <summary>
/// SVT: highbd_h_predictor
/// </summary>
/// <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));

26
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs

@ -3,13 +3,39 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Identifies the neighboring sample regions required by an AV1 intra-prediction mode.
/// </summary>
[Flags]
internal enum Av1NeighborNeed
{
/// <summary>
/// No neighboring samples are required.
/// </summary>
Nothing = 0,
/// <summary>
/// Samples immediately left of the block are required.
/// </summary>
Left = 2,
/// <summary>
/// Samples immediately above the block are required.
/// </summary>
Above = 4,
/// <summary>
/// Samples extending right of the top edge are required.
/// </summary>
AboveRight = 8,
/// <summary>
/// The sample diagonally above and left of the block is required.
/// </summary>
AboveLeft = 16,
/// <summary>
/// Samples extending below the left edge are required.
/// </summary>
BottomLeft = 32,
}

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

@ -6,26 +6,53 @@ 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 class 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"/> class 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"/> class 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));
@ -52,6 +79,13 @@ internal class Av1PaethPredictor : IAv1Predictor
}
}
/// <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;
@ -59,7 +93,7 @@ internal class Av1PaethPredictor : IAv1Predictor
int pTop = Av1Math.AbsoluteDifference(basis, top);
int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft);
// Return nearest to base of left, top and top_left.
// Ties prefer left, then top, matching AV1's normative Paeth selection order.
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft);
}
}

82
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs

@ -3,26 +3,104 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
// Inter modes are not defined here, as they do not apply to pictures.
/// <summary>
/// Identifies the intra-prediction modes used by an AV1 still-picture frame.
/// </summary>
/// <remarks>Inter modes are omitted because reduced still-picture frames do not reference other frames.</remarks>
internal enum Av1PredictionMode
{
/// <summary>
/// Predicts each sample from the average of the available top and left neighbors.
/// </summary>
DC,
/// <summary>
/// Repeats the top neighboring row vertically through the block.
/// </summary>
Vertical,
/// <summary>
/// Repeats the left neighboring column horizontally through the block.
/// </summary>
Horizontal,
/// <summary>
/// Projects neighboring samples into the block at 45 degrees.
/// </summary>
Directional45Degrees,
/// <summary>
/// Projects neighboring samples into the block at 135 degrees.
/// </summary>
Directional135Degrees,
/// <summary>
/// Projects neighboring samples into the block at 113 degrees.
/// </summary>
Directional113Degrees,
/// <summary>
/// Projects neighboring samples into the block at 157 degrees.
/// </summary>
Directional157Degrees,
/// <summary>
/// Projects neighboring samples into the block at 203 degrees.
/// </summary>
Directional203Degrees,
/// <summary>
/// Projects neighboring samples into the block at 67 degrees.
/// </summary>
Directional67Degrees,
/// <summary>
/// Blends horizontal and vertical smooth predictions.
/// </summary>
Smooth,
/// <summary>
/// Interpolates vertically between the top row and the bottom-left neighbor.
/// </summary>
SmoothVertical,
/// <summary>
/// Interpolates horizontally between the left column and the top-right neighbor.
/// </summary>
SmoothHorizontal,
/// <summary>
/// Selects the neighbor with the smallest gradient from the top-left reference.
/// </summary>
Paeth,
/// <summary>
/// Predicts chroma from the reconstructed luma AC surface.
/// </summary>
UvChromaFromLuma,
/// <summary>
/// The first luma intra-prediction mode.
/// </summary>
IntraModeStart = DC,
/// <summary>
/// The exclusive upper bound of luma intra-prediction modes.
/// </summary>
IntraModeEnd = Paeth + 1,
IntraModes = Paeth,
/// <summary>
/// The number of luma intra-prediction modes.
/// </summary>
IntraModes = Paeth + 1,
/// <summary>
/// The number of chroma intra-prediction modes, including chroma-from-luma.
/// </summary>
UvIntraModes = UvChromaFromLuma + 1,
/// <summary>
/// The invalid intra-mode sentinel matching the complete AV1 prediction-mode domain.
/// </summary>
IntraInvalid = 25,
}

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

@ -6,8 +6,14 @@ 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
@ -43,7 +49,17 @@ internal class Av1PredictorFactory
3, 0, 0, // 87, ...
];
internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
/// <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)
{
@ -69,13 +85,33 @@ internal class Av1PredictorFactory
}
}
internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn, int bitDepth)
/// <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>
/// SVT: svt_aom_highbd_dr_predictor
/// Predicts an 8-bit block by projecting reference-edge samples along a directional angle.
/// </summary>
internal static void DirectionalPredictor(Span<byte> destination, nuint stride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle)
/// <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);
@ -83,6 +119,8 @@ internal class Av1PredictorFactory
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);
@ -105,16 +143,56 @@ internal class Av1PredictorFactory
}
}
internal static void DirectionalPredictor(Span<short> destination, nuint stride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle, int bitDepth)
/// <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);
internal static void FilterIntraPredictor(Span<byte> destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, Av1FilterIntraMode filterIntraMode)
/// <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);
internal static void FilterIntraPredictor(Span<short> destination, nuint destinationStride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, Av1FilterIntraMode filterIntraMode, int bitDepth)
/// <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);
internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
/// <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)
{
@ -139,14 +217,24 @@ internal class Av1PredictorFactory
}
}
internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn)
/// <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);
// Get the shift (up-scaled by 256) in Y w.r.t a unit change in X.
// If angle > 0 && angle < 90, dy = 1;
// If angle > 90 && angle < 180, dy = (int32_t)(256 * t);
// If angle > 180 && angle < 270, dy = -((int32_t)(256 * t));
internal static int GetDeltaY(int angle)
/// <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)
{
@ -158,16 +246,17 @@ internal class Av1PredictorFactory
}
else
{
// In this case, we are not really going to use dy. We may return any value.
// Zones one and the exact horizontal/vertical modes never consume dy; one avoids a zero placeholder.
return 1;
}
}
// Get the shift (up-scaled by 256) in X w.r.t a unit change in Y.
// If angle > 0 && angle < 90, dx = -((int32_t)(256 / t));
// If angle > 90 && angle < 180, dx = (int32_t)(256 / t);
// If angle > 180 && angle < 270, dx = 1;
internal static int GetDeltaX(int angle)
/// <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)
{
@ -179,7 +268,7 @@ internal class Av1PredictorFactory
}
else
{
// In this case, we are not really going to use dx. We may return any value.
// Zone three and the exact horizontal/vertical modes never consume dx; one avoids a zero placeholder.
return 1;
}
}

32
src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs

@ -6,8 +6,14 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Provides transform, direction, angle, and neighbor metadata for AV1 intra-prediction modes.
/// </summary>
internal static class Av1PreditionModeExtensions
{
/// <summary>
/// Maps each luma intra-prediction mode to its default two-dimensional transform type.
/// </summary>
private static readonly Av1TransformType[] IntraPreditionMode2TransformType = [
Av1TransformType.DctDct, // DC
Av1TransformType.AdstDct, // V
@ -24,6 +30,9 @@ internal static class Av1PreditionModeExtensions
Av1TransformType.AdstAdst, // PAETH
];
/// <summary>
/// Maps each luma intra-prediction mode to the neighboring sample regions it consumes.
/// </summary>
private static readonly Av1NeighborNeed[] NeedsMap = [
Av1NeighborNeed.Above | Av1NeighborNeed.Left, // DC
Av1NeighborNeed.Above, // V
@ -40,6 +49,9 @@ internal static class Av1PreditionModeExtensions
Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // PAETH
];
/// <summary>
/// Maps each luma intra-prediction mode to its base directional angle in degrees, or zero for non-directional modes.
/// </summary>
private static readonly int[] AngleMap = [
0,
90,
@ -56,12 +68,32 @@ internal static class Av1PreditionModeExtensions
0,
];
/// <summary>
/// Gets the default transform type associated with an intra-prediction mode.
/// </summary>
/// <param name="mode">The luma intra-prediction mode.</param>
/// <returns>The default transform type.</returns>
public static Av1TransformType ToTransformType(this Av1PredictionMode mode) => IntraPreditionMode2TransformType[(int)mode];
/// <summary>
/// Determines whether an intra-prediction mode projects samples along a coded angle.
/// </summary>
/// <param name="mode">The luma intra-prediction mode.</param>
/// <returns><see langword="true"/> for a directional mode; otherwise, <see langword="false"/>.</returns>
public static bool IsDirectional(this Av1PredictionMode mode)
=> mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees;
/// <summary>
/// Gets the neighboring sample regions required by an intra-prediction mode.
/// </summary>
/// <param name="mode">The luma intra-prediction mode.</param>
/// <returns>The required neighboring sample flags.</returns>
public static Av1NeighborNeed GetNeighborNeed(this Av1PredictionMode mode) => NeedsMap[(int)mode];
/// <summary>
/// Gets the base prediction angle for an intra-prediction mode.
/// </summary>
/// <param name="mode">The luma intra-prediction mode.</param>
/// <returns>The prediction angle in degrees, or zero for a non-directional mode.</returns>
public static int ToAngle(this Av1PredictionMode mode) => AngleMap[(int)mode];
}

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

@ -6,29 +6,54 @@ 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 class 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"/> class 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"/> class 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);
/// <summary>
/// SVT: highbd_smooth_h_predictor
/// </summary>
/// <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));
@ -51,6 +76,8 @@ internal class Av1SmoothHorizontalPredictor : IAv1Predictor
{
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);

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

@ -6,12 +6,22 @@ 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 class 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.
@ -40,27 +50,49 @@ internal class Av1SmoothPredictor : IAv1Predictor
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"/> class 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"/> class 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);
/// <summary>
/// SVT: highbd_smooth_predictor
/// </summary>
/// <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));
@ -72,10 +104,10 @@ internal class Av1SmoothPredictor : IAv1Predictor
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.blockWidth];
ref int widthWeights = ref Weights[(int)this.blockHeight];
ref int heightWeights = ref Weights[(int)this.blockHeight];
ref int widthWeights = ref Weights[(int)this.blockWidth];
// scale = 2 * 2^sm_weight_log2_scale
// 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;
@ -88,6 +120,8 @@ internal class Av1SmoothPredictor : IAv1Predictor
{
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;

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

@ -6,29 +6,54 @@ 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 class 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"/> class 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"/> class 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);
/// <summary>
/// SVT: highbd_smooth_v_predictor
/// </summary>
/// <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));
@ -50,6 +75,7 @@ internal class Av1SmoothVerticalPredictor : IAv1Predictor
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);

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

@ -6,29 +6,54 @@ 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 class 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"/> class 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"/> class 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);
/// <summary>
/// SVT: highbd_v_predictor
/// </summary>
/// <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));

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

@ -4,16 +4,16 @@
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Interface for predictor implementations.
/// Defines scalar reconstruction of an 8-bit AV1 intra-prediction block from its neighboring samples.
/// </summary>
internal interface IAv1Predictor
{
/// <summary>
/// Predict using scalar logic within the 8-bit pipeline.
/// Writes the predicted block using scalar 8-bit arithmetic.
/// </summary>
/// <param name="destination">The destination to write to.</param>
/// <param name="stride">The stride of the destination buffer.</param>
/// <param name="above">Pointer to the first element of the block above.</param>
/// <param name="left">Pointer to the first element of the block to the left.</param>
/// <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);
}

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

@ -32,7 +32,7 @@ public class Av1PredictorTests
"7896fdbbfe538dce1dc3a5b0873d74b0", "504aea29c6b27f21555d5516b8de2d8a",
"c5738e7fa82b91ea0e39232120da56ea", "19abbd934c243a6d9df7585d81332dd5",
"9e42b7b342e45c842dfa8aedaddbdfaa", "0e9eb07a89f8bf96bc219d5d1c3d9f6d",
"659393c31633e0f498bae384c9df5c7b", "29241129BC91B354D04CA6C09A7CF1E1",
"659393c31633e0f498bae384c9df5c7b", "812DFE5C38F2B837529C5A918756E8B8",
];
private static string[] Digests8x4 = [
@ -56,7 +56,7 @@ public class Av1PredictorTests
"7911e2e02abfbe226f17529ac5db08fc", "064e509948982f66a14293f406d88d42",
"5c443aa713891406d5be3af4b3cf67c6", "5d2cb98e532822ca701110cda9ada968",
"3d58836e17918b8890012dd96b95bb9d", "20e8d61ddc451b9e553a294073349ffd",
"a9aa6cf9d0dcf1977a1853ccc264e40b", "FEDA9D1554325ED2A243FC3DD6ADD4E3",
"a9aa6cf9d0dcf1977a1853ccc264e40b", "D84707DAAF6CA99F041A670F16885CBA",
];
private static string[] Digests8x32 = [
@ -64,7 +64,7 @@ public class Av1PredictorTests
"f337dce3980f70730d6f6c2c756e3b62", "796189b05dc026e865c9e95491b255d1",
"ea932c21e7189eeb215c1990491320ab", "a9fffdf9455eba5e3b01317cae140289",
"9525dbfdbf5fba61ef9c7aa5fe887503", "8c6a7e3717ff8a459f415c79bb17341c",
"3761071bfaa2363a315fe07223f95a2d", "506D691319D1AEF38DF5C1E060408BDD",
"3761071bfaa2363a315fe07223f95a2d", "25DADBA40EBC6C4616A7A536AC48CAA6",
];
private static string[] Digests16x4 = [
@ -72,7 +72,7 @@ public class Av1PredictorTests
"28a6af15e31f76d3ff189012475d78f5", "e330d67b859bceef62b96fc9e1f49a34",
"36eca3b8083ce2fb5f7e6227dfc34e71", "08f567d2abaa8e83e4d9b33b3f709538",
"dc2d0ba13aa9369446932f03b53dc77d", "9ab342944c4b1357aa79d39d7bebdd3a",
"77ec278c5086c88b91d68eef561ed517", "7B8ECDFBF449908E9C96664582C05BFE",
"77ec278c5086c88b91d68eef561ed517", "E4726C9838383FAC75A028303808EABF",
];
private static string[] Digests16x8 = [
@ -80,7 +80,7 @@ public class Av1PredictorTests
"728d61c11b06baf7fe77881003a918b9", "889997b89a44c9976cb34f573e2b1eea",
"b43bfc31d1c770bb9ca5ca158c9beec4", "9d3fe9f762e0c6e4f114042147c50c7f",
"c74fdd7c9938603b01e7ecf9fdf08d61", "870c7336db1102f80f74526bd5a7cf4e",
"3fd5354a6190903d6a0b661fe177daf6", "EA586FE5C31B38A547D18332141665E9",
"3fd5354a6190903d6a0b661fe177daf6", "02BB1B12EC2CD3D9E08706D177309B25",
];
private static string[] Digests16x16 = [
@ -96,7 +96,7 @@ public class Av1PredictorTests
"bc05c46f18d0638f0228f1de64f07cd5", "204e613e429935f721a5b29cec7d44bb",
"aa0a7c9a7482dfc06d9685072fc5bafd", "ffb60f090d83c624bb4f7dc3a630ac4f",
"36bcb9ca9bb5eac520b050409de25da5", "34d9a5dd3363668391bc3bd05b468182",
"1e149c28db8b234e43931c347a523794", "DD9FCEB000F2B2F1F4910AE9856BCF4C",
"1e149c28db8b234e43931c347a523794", "1C0AF3C1B39A4C1866440D4C80A8CE8E",
];
private static string[] Digests16x64 = [
@ -104,7 +104,7 @@ public class Av1PredictorTests
"12b0c69595328c465e0b25e0c9e3e9fc", "3b2a053ee8b05a8ac35ad23b0422a151",
"f3be77c0fe67eb5d9d515e92bec21eb7", "f1ece6409e01e9dd98b800d49628247d",
"efd2ec9bfbbd4fd1f6604ea369df1894", "ec703de918422b9e03197ba0ed60a199",
"739418efb89c07f700895deaa5d0b3e3", "AE9A0FBC3EB929B82E20E8C45001E7BE",
"739418efb89c07f700895deaa5d0b3e3", "272C2EAFEABCDA41F27EFBB48B938211",
];
private static string[] Digests32x8 = [
@ -112,7 +112,7 @@ public class Av1PredictorTests
"79b799f1eb77d5189535dc4e18873a0e", "90e943adf3de4f913864dce4e52b4894",
"5e1b9cc800a89ef45f5bdcc9e99e4e96", "3103405df20d254cbf32ac30872ead4b",
"648550e369b77687bff3c7d6f249b02f", "f9f73bcd8aadfc059fa260325df957a1",
"204cef70d741c25d4fe2b1d10d2649a5", "8CB3C74FFFF9975F3DE5178311486350",
"204cef70d741c25d4fe2b1d10d2649a5", "A71D39B79759B44AF95E3C03D2D72C56",
];
private static string[] Digests32x16 = [
@ -120,7 +120,7 @@ public class Av1PredictorTests
"6ad3bb37ebe8374b0a4c2d18fe3ebb6a", "08d3cfe7a1148bff55eb6166da3378c6",
"656a722394764d17b6c42401b9e0ad3b", "4aa00c192102efeb325883737e562f0d",
"9881a90ca88bca4297073e60b3bb771a", "8cd74aada398a3d770fc3ace38ecd311",
"0a927e3f5ff8e8338984172cc0653b13", "91ABCB84EB86746DEF31AF8F96BAA0CF",
"0a927e3f5ff8e8338984172cc0653b13", "B48D4391B5637D085F128C7137739400",
];
private static string[] Digests32x32 = [
@ -136,7 +136,7 @@ public class Av1PredictorTests
"4e2a2cfd8f56f15939bdfc753145b303", "0ce332b343934b34cd4417725faa85cb",
"1d2f8e48e3adb7c448be05d9f66f4954", "9fb2e176636a5689b26f73ca73fcc512",
"e720ebccae7e25e36f23da53ae5b5d6a", "86fe4364734169aaa4520d799890d530",
"b1870290764bb1b100d1974e2bd70f1d", "B652BBE65C345A7153A1F3E1C801633F",
"b1870290764bb1b100d1974e2bd70f1d", "2579B3CD9B1252A282C79C36B47FA186",
];
private static string[] Digests64x16 = [
@ -144,7 +144,7 @@ public class Av1PredictorTests
"69e462c3338a9aaf993c3f7cfbc15649", "821b76b1494d4f84d20817840f719a1a",
"fd9b4276e7affe1e0e4ce4f428058994", "cd82fd361a4767ac29a9f406b480b8f3",
"2792c2f810157a4a6cb13c28529ff779", "1220442d90c4255ba0969d28b91e93a6",
"c7253e10b45f7f67dfee3256c9b94825", "F0DC6EE8E9291452AC361E08AEB53DB5",
"c7253e10b45f7f67dfee3256c9b94825", "103DA3694F8D5C6F79CA854418F3E3F9",
];
private static string[] Digests64x32 = [
@ -152,7 +152,7 @@ public class Av1PredictorTests
"538424b24bd0830f21788e7238ca762f", "a6c5aeb722615089efbca80b02951ceb",
"12604b37875533665078405ef4582e35", "0048afa17bd3e1632d68b96048836530",
"07a0cfcb56a5eed50c4bd6c26814336b", "529d8a070de5bc6531fa3ee8f450c233",
"33c50a11c7d78f72434064f634305e95", "28E560D9C16C5ED6055A66AB16EC6900",
"33c50a11c7d78f72434064f634305e95", "98BEB315BF0A9B734D4FB137FDE00DF4",
];
private static string[] Digests64x64 = [

Loading…
Cancel
Save