Browse Source

Implement remaining intra predictors

pull/2633/head
Ynse Hoornenborg 1 year ago
parent
commit
4b39c1a7bb
  1. 4
      src/ImageSharp/Formats/Heif/Av1/Av1Math.cs
  2. 4
      src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs
  3. 23
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs
  4. 79
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone1Predictor.cs
  5. 79
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone2Predictor.cs
  6. 78
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DirectionalZone3Predictor.cs
  7. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HorizontalPredictor.cs
  8. 59
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PaethPredictor.cs
  9. 11
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs
  10. 135
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  11. 63
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothHorizontalPredictor.cs
  12. 101
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothPredictor.cs
  13. 62
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1SmoothVerticalPredictor.cs
  14. 47
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1VerticalPredictor.cs
  15. 10
      src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs
  16. 17
      src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs
  17. 104
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs
  18. 210
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

4
src/ImageSharp/Formats/Heif/Av1/Av1Math.cs

@ -202,4 +202,8 @@ internal static class Av1Math
internal static void SetBit(ref int endOfBlockExtra, int n)
=> endOfBlockExtra |= 1 << n;
internal static int AbsoluteDifference(int a, int b) => (a > b) ? a - b : b - a;
internal static int DivideRound(int value, int bitCount) => (value + (1 << (bitCount - 1))) >> bitCount;
}

4
src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs

@ -243,6 +243,10 @@ internal class ObuReader
}
}
/// <summary>
/// 5.9.9. Compute image size function.
/// </summary>
/// <remarks>SVT: compute_image_size</remarks>
private void ComputeImageSize(ObuSequenceHeader sequenceHeader)
{
ObuFrameHeader frameHeader = this.FrameHeader!;

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

@ -8,19 +8,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1DcPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DcPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DcPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
this.blockWidth = (nuint)transformSize.GetWidth();
this.blockHeight = (nuint)transformSize.GetHeight();
}
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left)
@ -36,21 +36,22 @@ internal class Av1DcPredictor : IAv1Predictor
ref byte leftRef = ref left[0];
ref byte aboveRef = ref above[0];
ref byte destinationRef = ref destination[0];
uint count = this.blockWidth + this.blockHeight;
for (uint i = 0; i < this.blockWidth; i++)
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 (uint i = 0; i < this.blockHeight; i++)
for (nuint i = 0; i < this.blockHeight; i++)
{
sum += Unsafe.Add(ref leftRef, i);
}
byte expectedDc = (byte)((sum + (count >> 1)) / count);
for (uint r = 0; r < this.blockHeight; r++)
for (nuint r = 0; r < this.blockHeight; r++)
{
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth);
Unsafe.InitBlock(ref destinationRef, expectedDc, width);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}

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

@ -0,0 +1,79 @@
// 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;
internal class Av1DirectionalZone1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone1Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DirectionalZone1Predictor(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> above, bool upsampleAbove, int dx)
=> new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx);
/// <summary>
/// SVT: svt_av1_dr_prediction_z1_c
/// </summary>
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)
{
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);
}
}
}

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

@ -0,0 +1,79 @@
// 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;
internal class Av1DirectionalZone2Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone2Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1DirectionalZone2Predictor(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> above, Span<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy)
=> new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleAbove, dx, dy);
/// <summary>
/// SVT: svt_av1_dr_prediction_z1_c
/// </summary>
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)
{
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);
}
}
}

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

@ -0,0 +1,78 @@
// 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;
internal class Av1DirectionalZone3Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1DirectionalZone3Predictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
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>
/// SVT: svt_av1_dr_prediction_z3_c
/// </summary>
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)
{
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;
}
}
}

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

@ -0,0 +1,47 @@
// 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;
internal class Av1HorizontalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1HorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1HorizontalPredictor(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> above, Span<byte> left)
=> new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_h_predictor
/// </summary>
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);
}
}
}

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

@ -0,0 +1,59 @@
// 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;
internal class Av1PaethPredictor : IAv1Predictor
{
private readonly uint blockWidth;
private readonly uint blockHeight;
public Av1PaethPredictor(Size blockSize)
{
this.blockWidth = (uint)blockSize.Width;
this.blockHeight = (uint)blockSize.Height;
}
public Av1PaethPredictor(Av1TransformSize transformSize)
{
this.blockWidth = (uint)transformSize.GetWidth();
this.blockHeight = (uint)transformSize.GetHeight();
}
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);
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];
int yTopLeft = above[-1];
ref byte destinationRef = ref destination[0];
for (nuint r = 0; r < this.blockHeight; r++)
{
for (nuint c = 0; c < this.blockWidth; c++)
{
destinationRef = PredictSingle(Unsafe.Add(ref leftRef, r), Unsafe.Add(ref aboveRef, c), yTopLeft);
destinationRef = ref Unsafe.Add(ref destinationRef, stride);
}
}
}
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);
// Return nearest to base of left, top and top_left.
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft);
}
}

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

@ -868,18 +868,24 @@ internal class Av1PredictionDecoder
}
}
/// <summary>
/// SVT: svt_aom_use_intra_edge_upsample
/// </summary>
private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool type)
{
int d = Math.Abs(delta);
int widthHeight = width + height;
if (d is <= 0 or >= 40)
{
return false;
}
int widthHeight = width + height;
return type ? (widthHeight <= 8) : (widthHeight <= 16);
}
/// <summary>
/// SVT: svt_av1_filter_intra_edge_c
/// </summary>
private static void FilterIntraEdge(ref byte buffer, int count, int strength)
{
// TODO: Consider creating SIMD version
@ -911,6 +917,9 @@ internal class Av1PredictionDecoder
}
}
/// <summary>
/// SVT: svt_aom_intra_edge_filter_strength
/// </summary>
private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType)
{
int d = Math.Abs(delta);

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

@ -8,6 +8,41 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
internal class Av1PredictorFactory
{
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, ...
];
internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn)
{
if (hasLeft)
@ -34,9 +69,105 @@ internal class Av1PredictorFactory
}
}
internal static void DirectionalPredictor(Span<byte> destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException();
/// <summary>
/// SVT: svt_aom_highbd_dr_predictor
/// </summary>
internal 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));
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);
}
}
internal static void FilterIntraPredictor(Span<byte> destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException();
internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn) => throw new NotImplementedException();
internal 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;
}
}
// 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));
private 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
{
// In this case, we are not really going to use dy. We may return any value.
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;
private 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
{
// In this case, we are not really going to use dx. We may return any value.
return 1;
}
}
}

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

@ -0,0 +1,63 @@
// 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;
internal class Av1SmoothHorizontalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothHorizontalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothHorizontalPredictor(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> above, Span<byte> left)
=> new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_h_predictor
/// </summary>
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];
// scale = 2 * 2^sm_weight_log2_scale
int log2Scale = 1 + 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));
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);
}
}
}

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

@ -0,0 +1,101 @@
// 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;
internal class Av1SmoothPredictor : IAv1Predictor
{
// Weights are quadratic from '1' to '1 / BlockSize', scaled by
// 2^sm_weight_log2_scale.
internal static readonly int WeightLog2Scale = 8;
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,
];
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothPredictor(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> above, Span<byte> left)
=> new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_predictor
/// </summary>
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.blockWidth];
ref int widthWeights = ref Weights[(int)this.blockHeight];
// scale = 2 * 2^sm_weight_log2_scale
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));
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);
}
}
}

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

@ -0,0 +1,62 @@
// 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;
internal class Av1SmoothVerticalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1SmoothVerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1SmoothVerticalPredictor(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> above, Span<byte> left)
=> new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_smooth_v_predictor
/// </summary>
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];
// scale = 2 * 2^sm_weight_log2_scale
int log2Scale = 1 + 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)
{
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);
}
}
}

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

@ -0,0 +1,47 @@
// 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;
internal class Av1VerticalPredictor : IAv1Predictor
{
private readonly nuint blockWidth;
private readonly nuint blockHeight;
public Av1VerticalPredictor(Size blockSize)
{
this.blockWidth = (nuint)blockSize.Width;
this.blockHeight = (nuint)blockSize.Height;
}
public Av1VerticalPredictor(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> above, Span<byte> left)
=> new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left);
/// <summary>
/// SVT: highbd_v_predictor
/// </summary>
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);
}
}
}

10
src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs

@ -169,11 +169,7 @@ internal class Av1TileReader : IAv1TileReader
{
int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo);
partitionType = Av1PartitionType.Split;
if (blockSize < Av1BlockSize.Block8x8)
{
partitionType = Av1PartitionType.None;
}
else if (hasRows && hasColumns)
if (hasRows && hasColumns)
{
partitionType = reader.ReadPartitionType(ctx);
}
@ -376,6 +372,7 @@ internal class Av1TileReader : IAv1TileReader
/// <summary>
/// 5.11.34. Residual syntax.
/// </summary>
/// <remarks>SVT: parse_residual</remarks>
private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize)
{
int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false);
@ -813,6 +810,7 @@ internal class Av1TileReader : IAv1TileReader
/// <summary>
/// Section 5.11.16. Block TX size syntax.
/// </summary>
/// <remarks>SVT: read_block_tx_size</remarks>
private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo)
{
Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize;
@ -1318,7 +1316,7 @@ internal class Av1TileReader : IAv1TileReader
int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2();
int above = (aboveCtx >> blockSizeLog) & 0x1;
int left = (leftCtx >> blockSizeLog) & 0x1;
DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square");
DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square.");
DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog));
return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet);
}

17
src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs

@ -163,19 +163,12 @@ internal static class Av1InverseTransformMath
return ClipPixelHighBitDepth(dest + trans, bitDepth);
}
private static short ClipPixelHighBitDepth(long val, int bd)
private static short ClipPixelHighBitDepth(long val, int bd) => bd switch
{
switch (bd)
{
case 8:
default:
return (short)Av1Math.Clamp(val, 0, 255);
case 10:
return (short)Av1Math.Clamp(val, 0, 1023);
case 12:
return (short)Av1Math.Clamp(val, 0, 4095);
}
}
10 => (short)Av1Math.Clamp(val, 0, 1023),
12 => (short)Av1Math.Clamp(val, 0, 4095),
_ => (short)Av1Math.Clamp(val, 0, 255),
};
public static void RoundShiftArray(Span<int> arr, int size, int bit)
{

104
tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraPredictionMemory.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
internal class Av1IntraPredictionMemory
{
public const int Padding = 16;
private const int TopPadding = 7;
private const int MaxBlockSizeTimes2 = 1 << Av1Constants.MaxSuperBlockSizeLog2;
private const int TotalPixelCount = 4096;
private readonly byte[] destination = new byte[TotalPixelCount];
private readonly byte[] referenceSource = new byte[TotalPixelCount];
private readonly byte[] leftMemory = new byte[MaxBlockSizeTimes2 + Padding];
private readonly byte[] topMemory = new byte[MaxBlockSizeTimes2 + Padding + TopPadding];
private readonly int bitDepth;
public Av1IntraPredictionMemory(int bitDepth) => this.bitDepth = bitDepth;
public Span<byte> Destination => this.destination;
public Span<byte> Left => this.leftMemory;
public Span<byte> Top => this.topMemory;
/// <summary>
/// Sets referenceSource, left and top to <paramref name="value"/>.
/// </summary>
public void Set(byte value)
{
for (int r = 0; r < this.referenceSource.Length; r++)
{
this.referenceSource[r] = value;
}
// Upsampling in the directional predictors extends left/top[-1] to [-2].
for (int i = Padding - 2; i < Padding + MaxBlockSizeTimes2; ++i)
{
this.leftMemory[i] = this.topMemory[i] = value;
}
}
public void Scramble(Random rnd)
{
for (int i = 0; i < this.referenceSource.Length; i++)
{
this.referenceSource[i] = this.GetRandomValue(rnd);
}
for (int i = Padding; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
{
this.leftMemory[i] = this.GetRandomValue(rnd);
}
for (int i = Padding - 1; i < (MaxBlockSizeTimes2 >> 1) + Padding; ++i)
{
this.topMemory[i] = this.GetRandomValue(rnd);
}
// Some directional predictors require top-right, bottom-left.
for (int i = MaxBlockSizeTimes2 >> 1; i < MaxBlockSizeTimes2; ++i)
{
this.leftMemory[Padding + i] = this.GetRandomValue(rnd);
this.topMemory[Padding + i] = this.GetRandomValue(rnd);
}
// TODO(jzern): reorder this and regenerate the digests after switching
// random number generators.
// Upsampling in the directional predictors extends left/top[-1] to [-2].
this.leftMemory[Padding - 1] = this.GetRandomValue(rnd);
this.leftMemory[Padding - 2] = this.GetRandomValue(rnd);
this.topMemory[Padding - 2] = this.GetRandomValue(rnd);
this.leftMemory.AsSpan(0, Padding - 2).Clear();
this.topMemory.AsSpan(0, Padding - 2).Clear();
this.topMemory.AsSpan(MaxBlockSizeTimes2 + Padding, TopPadding).Clear();
}
public void CopySourceToDestination() => this.referenceSource.CopyTo(this.destination.AsSpan());
/// <summary>
/// Return a hash string of a block of bytes.
/// </summary>
/// <remarks>This test design is used extensively in 'libgav1' tests, some of which are ported here.</remarks>
public static string GetDigest(byte[] input)
{
byte[] hash = MD5.HashData(input);
StringBuilder sb = new();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
public string GetDestinationDigest() => GetDigest(this.destination);
private byte GetRandomValue(Random random) => (byte)(random.Next(1 << 16) & ((1 << this.bitDepth) - 1));
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
@ -9,9 +10,161 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
[Trait("Format", "Avif")]
public class Av1PredictorTests
{
private static string[] Digests4x4 = [
"7b1c762e28747f885d2b7d83cb8aa75c", "73353f179207f1432d40a132809e3a50",
"80c9237c838b0ec0674ccb070df633d5", "1cd79116b41fda884e7fa047f5eb14df",
"33211425772ee539a59981a2e9dc10c1", "d6f5f65a267f0e9a2752e8151cc1dcd7",
"7ff8c762cb766eb0665682152102ce4b", "2276b861ae4599de15938651961907ec",
"766982bc69f4aaaa8e71014c2dc219bc", "04401B397D702F853B12407EBFB91027",
];
private static string[] Digests4x8 = [
"0a0d8641ecfa0e82f541acdc894d5574", "1a40371af6cff9c278c5b0def9e4b3e7",
"3631a7a99569663b514f15b590523822", "646c7b592136285bd31501494e7393e7",
"ecbe89cc64dc2688123d3cfe865b5237", "79048e70ecbb7d43a4703f62718588c0",
"f3de11bf1198a00675d806d29c41d676", "32bb6cd018f6e871c342fcc21c7180cf",
"6f076a1e5ab3d69cf08811d62293e4be", "F94348B0D3E2B1F2FC061232831E8B9B",
];
private static string[] Digests4x16 = [
"cb8240be98444ede5ae98ca94afc1557", "460acbcf825a1fa0d8f2aa6bf2d6a21c",
"7896fdbbfe538dce1dc3a5b0873d74b0", "504aea29c6b27f21555d5516b8de2d8a",
"c5738e7fa82b91ea0e39232120da56ea", "19abbd934c243a6d9df7585d81332dd5",
"9e42b7b342e45c842dfa8aedaddbdfaa", "0e9eb07a89f8bf96bc219d5d1c3d9f6d",
"659393c31633e0f498bae384c9df5c7b", "29241129BC91B354D04CA6C09A7CF1E1",
];
private static string[] Digests8x4 = [
"5950744064518f77867c8e14ebd8b5d7", "46b6cbdc76efd03f4ac77870d54739f7",
"efe21fd1b98cb1663950e0bf49483b3b", "3c647b64760b298092cbb8e2f5c06bfd",
"c3595929687ffb04c59b128d56e2632f", "d89ad2ddf8a74a520fdd1d7019fd75b4",
"53907cb70ad597ee5885f6c58201f98b", "09d2282a29008b7fb47eb60ed6653d06",
"e341fc1c910d7cb2dac5dbc58b9c9af9", "93D67CD49B07CE3870526E5C12F00EBF",
];
private static string[] Digests8x8 = [
"06fb7cb52719855a38b4883b4b241749", "2013aafd42a4303efb553e42264ab8b0",
"2f070511d5680c12ca73a20e47fd6e23", "9923705af63e454392625794d5459fe0",
"04007a0d39778621266e2208a22c4fac", "2d296c202d36b4a53f1eaddda274e4a1",
"c87806c220d125c7563c2928e836fbbd", "339b49710a0099087e51ab5afc8d8713",
"c90fbc020afd9327bf35dccae099bf77", "BAB0E5918AB2F7354D9BCA4E9A927C0F",
];
private static string[] Digests8x16 = [
"3c5a4574d96b5bb1013429636554e761", "8cf56b17c52d25eb785685f2ab48b194",
"7911e2e02abfbe226f17529ac5db08fc", "064e509948982f66a14293f406d88d42",
"5c443aa713891406d5be3af4b3cf67c6", "5d2cb98e532822ca701110cda9ada968",
"3d58836e17918b8890012dd96b95bb9d", "20e8d61ddc451b9e553a294073349ffd",
"a9aa6cf9d0dcf1977a1853ccc264e40b", "FEDA9D1554325ED2A243FC3DD6ADD4E3",
];
private static string[] Digests8x32 = [
"b393a2db7a76acaccc39e04d9dc3e8ac", "bbda713ee075a7ef095f0f479b5a1f82",
"f337dce3980f70730d6f6c2c756e3b62", "796189b05dc026e865c9e95491b255d1",
"ea932c21e7189eeb215c1990491320ab", "a9fffdf9455eba5e3b01317cae140289",
"9525dbfdbf5fba61ef9c7aa5fe887503", "8c6a7e3717ff8a459f415c79bb17341c",
"3761071bfaa2363a315fe07223f95a2d", "506D691319D1AEF38DF5C1E060408BDD",
];
private static string[] Digests16x4 = [
"1c0a950b3ac500def73b165b6a38467c", "95e7f7300f19da280c6a506e40304462",
"28a6af15e31f76d3ff189012475d78f5", "e330d67b859bceef62b96fc9e1f49a34",
"36eca3b8083ce2fb5f7e6227dfc34e71", "08f567d2abaa8e83e4d9b33b3f709538",
"dc2d0ba13aa9369446932f03b53dc77d", "9ab342944c4b1357aa79d39d7bebdd3a",
"77ec278c5086c88b91d68eef561ed517", "7B8ECDFBF449908E9C96664582C05BFE",
];
private static string[] Digests16x8 = [
"053a2bc4b5b7287fee524af4e77f077a", "619b720b13f14f32391a99ea7ff550d5",
"728d61c11b06baf7fe77881003a918b9", "889997b89a44c9976cb34f573e2b1eea",
"b43bfc31d1c770bb9ca5ca158c9beec4", "9d3fe9f762e0c6e4f114042147c50c7f",
"c74fdd7c9938603b01e7ecf9fdf08d61", "870c7336db1102f80f74526bd5a7cf4e",
"3fd5354a6190903d6a0b661fe177daf6", "EA586FE5C31B38A547D18332141665E9",
];
private static string[] Digests16x16 = [
"1fa9e2086f6594bda60c30384fbf1635", "2098d2a030cd7c6be613edc74dc2faf8",
"f3c72b0c8e73f1ddca04d14f52d194d8", "6b31f2ee24cf88d3844a2fc67e1f39f3",
"d91a22a83575e9359c5e4871ab30ddca", "24c32a0d38b4413d2ef9bf1f842c8634",
"6e9e47bf9da9b2b9ae293e0bbd8ff086", "968b82804b5200b074bcdba9718140d4",
"4e6d7e612c5ae0bbdcc51a453cd1db3f", "D182AFFEC5DBD0C7C0BD945AF090C48D",
];
private static string[] Digests16x32 = [
"01afd04432026ff56327d6226b720be2", "a6e7be906cc6f1e7a520151bfa7c303d",
"bc05c46f18d0638f0228f1de64f07cd5", "204e613e429935f721a5b29cec7d44bb",
"aa0a7c9a7482dfc06d9685072fc5bafd", "ffb60f090d83c624bb4f7dc3a630ac4f",
"36bcb9ca9bb5eac520b050409de25da5", "34d9a5dd3363668391bc3bd05b468182",
"1e149c28db8b234e43931c347a523794", "DD9FCEB000F2B2F1F4910AE9856BCF4C",
];
private static string[] Digests16x64 = [
"727797ef15ccd8d325476fe8f12006a3", "f77c544ac8035e01920deae40cee7b07",
"12b0c69595328c465e0b25e0c9e3e9fc", "3b2a053ee8b05a8ac35ad23b0422a151",
"f3be77c0fe67eb5d9d515e92bec21eb7", "f1ece6409e01e9dd98b800d49628247d",
"efd2ec9bfbbd4fd1f6604ea369df1894", "ec703de918422b9e03197ba0ed60a199",
"739418efb89c07f700895deaa5d0b3e3", "AE9A0FBC3EB929B82E20E8C45001E7BE",
];
private static string[] Digests32x8 = [
"4da55401331ed98acec0c516d7307513", "0ae6f3974701a5e6c20baccd26b4ca52",
"79b799f1eb77d5189535dc4e18873a0e", "90e943adf3de4f913864dce4e52b4894",
"5e1b9cc800a89ef45f5bdcc9e99e4e96", "3103405df20d254cbf32ac30872ead4b",
"648550e369b77687bff3c7d6f249b02f", "f9f73bcd8aadfc059fa260325df957a1",
"204cef70d741c25d4fe2b1d10d2649a5", "8CB3C74FFFF9975F3DE5178311486350",
];
private static string[] Digests32x16 = [
"86ad1e1047abaf9959150222e8f19593", "1908cbe04eb4e5c9d35f1af7ffd7ee72",
"6ad3bb37ebe8374b0a4c2d18fe3ebb6a", "08d3cfe7a1148bff55eb6166da3378c6",
"656a722394764d17b6c42401b9e0ad3b", "4aa00c192102efeb325883737e562f0d",
"9881a90ca88bca4297073e60b3bb771a", "8cd74aada398a3d770fc3ace38ecd311",
"0a927e3f5ff8e8338984172cc0653b13", "91ABCB84EB86746DEF31AF8F96BAA0CF",
];
private static string[] Digests32x32 = [
"1303ca680644e3d8c9ffd4185bb2835b", "2a4d9f5cc8da307d4cf7dc021df10ba9",
"ced60d3f4e4b011a6a0314dd8a4b1fd8", "ced60d3f4e4b011a6a0314dd8a4b1fd8",
"1464b01aa928e9bd82c66bad0f921693", "90deadfb13d7c3b855ba21b326c1e202",
"af96a74f8033dff010e53a8521bc6f63", "9f1039f2ef082aaee69fcb7d749037c2",
"3f82893e478e204f2d254b34222d14dc", "9B2A16A6331AA6EC41636B8F709B39C7",
];
private static string[] Digests32x64 = [
"e1e8ed803236367821981500a3d9eebe", "0f46d124ba9f48cdd5d5290acf786d6d",
"4e2a2cfd8f56f15939bdfc753145b303", "0ce332b343934b34cd4417725faa85cb",
"1d2f8e48e3adb7c448be05d9f66f4954", "9fb2e176636a5689b26f73ca73fcc512",
"e720ebccae7e25e36f23da53ae5b5d6a", "86fe4364734169aaa4520d799890d530",
"b1870290764bb1b100d1974e2bd70f1d", "B652BBE65C345A7153A1F3E1C801633F",
];
private static string[] Digests64x16 = [
"de1b736e9d99129609d6ef3a491507a0", "516d8f6eb054d74d150e7b444185b6b9",
"69e462c3338a9aaf993c3f7cfbc15649", "821b76b1494d4f84d20817840f719a1a",
"fd9b4276e7affe1e0e4ce4f428058994", "cd82fd361a4767ac29a9f406b480b8f3",
"2792c2f810157a4a6cb13c28529ff779", "1220442d90c4255ba0969d28b91e93a6",
"c7253e10b45f7f67dfee3256c9b94825", "F0DC6EE8E9291452AC361E08AEB53DB5",
];
private static string[] Digests64x32 = [
"e48e1ac15e97191a8fda08d62fff343e", "80c15b303235f9bc2259027bb92dfdc4",
"538424b24bd0830f21788e7238ca762f", "a6c5aeb722615089efbca80b02951ceb",
"12604b37875533665078405ef4582e35", "0048afa17bd3e1632d68b96048836530",
"07a0cfcb56a5eed50c4bd6c26814336b", "529d8a070de5bc6531fa3ee8f450c233",
"33c50a11c7d78f72434064f634305e95", "28E560D9C16C5ED6055A66AB16EC6900",
];
private static string[] Digests64x64 = [
"a1650dbcd56e10288c3e269eca37967d", "be91585259bc37bf4dc1651936e90b3e",
"afe020786b83b793c2bbd9468097ff6e", "6e1094fa7b50bc813aa2ba29f5df8755",
"9e5c34f3797e0cdd3cd9d4c05b0d8950", "bc87be7ac899cc6a28f399d7516c49fe",
"9811fd0d2dd515f06122f5d1bd18b784", "3c140e466f2c2c0d9cb7d2157ab8dc27",
"9543de76c925a8f6adc884cc7f98dc91", "73BBEC1CDFFF6D66318FB43628EA42C2",
];
[Theory]
[MemberData(nameof(GetTransformSizes))]
public void VerifyDcFill(int width, int height)
public void VerifyDcFill(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@ -29,7 +182,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
public void VerifyDc(int width, int height)
public void VerifyDc(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@ -52,7 +205,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
public void VerifyDcLeft(int width, int height)
public void VerifyDcLeft(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@ -72,7 +225,7 @@ public class Av1PredictorTests
[Theory]
[MemberData(nameof(GetTransformSizes))]
public void VerifyDcTop(int width, int height)
public void VerifyDcTop(int _, int width, int height)
{
// Assign
byte[] destination = new byte[width * height];
@ -90,6 +243,25 @@ public class Av1PredictorTests
Assert.All(destination, (b) => AssertValue(expected, b));
}
[Theory]
[MemberData(nameof(GetTransformSizes))]
public void VerifySmooth(int index, int width, int height)
{
// Arrange
string expectedDigest = GetExpectedDigext((Av1TransformSize)index, Av1PredictionMode.Smooth);
Av1IntraPredictionMemory predictorMemory = new(8);
predictorMemory.Scramble(new Random(42));
predictorMemory.CopySourceToDestination();
const int stride = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - 1);
Av1SmoothPredictor predictor = new(new Size(width, height));
// Act
predictor.PredictScalar(predictorMemory.Destination, stride, predictorMemory.Top, predictorMemory.Left);
// Assert
Assert.Equal(expectedDigest, predictorMemory.GetDestinationDigest());
}
private static void AssertValue(byte expected, byte actual)
{
Assert.NotEqual(0, actual);
@ -107,17 +279,41 @@ public class Av1PredictorTests
return sum;
}
public static TheoryData<int, int> GetTransformSizes()
public static TheoryData<int, int, int> GetTransformSizes()
{
TheoryData<int, int> combinations = [];
TheoryData<int, int, int> combinations = [];
for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++)
{
Av1TransformSize size = (Av1TransformSize)s;
int width = size.GetWidth();
int height = size.GetHeight();
combinations.Add(width, height);
combinations.Add(s, width, height);
}
return combinations;
}
private static string GetExpectedDigext(Av1TransformSize size, Av1PredictionMode mode) => size switch
{
Av1TransformSize.Size4x4 => Digests4x4[(int)mode],
Av1TransformSize.Size8x4 => Digests8x4[(int)mode],
Av1TransformSize.Size4x8 => Digests4x8[(int)mode],
Av1TransformSize.Size8x8 => Digests8x8[(int)mode],
Av1TransformSize.Size4x16 => Digests4x16[(int)mode],
Av1TransformSize.Size16x4 => Digests16x4[(int)mode],
Av1TransformSize.Size8x16 => Digests8x16[(int)mode],
Av1TransformSize.Size16x8 => Digests16x8[(int)mode],
Av1TransformSize.Size16x16 => Digests16x16[(int)mode],
Av1TransformSize.Size8x32 => Digests8x32[(int)mode],
Av1TransformSize.Size32x8 => Digests32x8[(int)mode],
Av1TransformSize.Size16x32 => Digests16x32[(int)mode],
Av1TransformSize.Size32x16 => Digests32x16[(int)mode],
Av1TransformSize.Size32x32 => Digests32x32[(int)mode],
Av1TransformSize.Size16x64 => Digests16x64[(int)mode],
Av1TransformSize.Size64x16 => Digests64x16[(int)mode],
Av1TransformSize.Size32x64 => Digests32x64[(int)mode],
Av1TransformSize.Size64x32 => Digests64x32[(int)mode],
Av1TransformSize.Size64x64 => Digests64x64[(int)mode],
_ => string.Empty,
};
}

Loading…
Cancel
Save