Browse Source

Implement high-bit-depth AV1 intra prediction

pull/2633/head
James Jackson-South 2 months ago
parent
commit
361cdb29db
  1. 2
      HEIF_IMPLEMENTATION_PLAN.md
  2. 2
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1FilterIntraPredictor.cs
  3. 423
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1HighBitDepthPredictor.cs
  4. 16
      src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs
  5. 131
      tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs

2
HEIF_IMPLEMENTATION_PLAN.md

@ -58,7 +58,7 @@ This snapshot pins or classifies the available references and failures; it does
| --- | --- | --- | --- |
| `Av1YuvConverter.ConvertToRgb`, `ConvertFromRgb`, scalar row conversion, and chroma reconstruction | H.273 formulas 20-31 and the identity, YCgCo, and non-constant-luminance matrix formulas; AV1 section 6.4.2 chroma sample positions | libavif `src/reformat.c` and `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826`; libaom `aom/aom_image.h` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar behavioral oracle for 8-bit full/limited-range conversion. Decode covers monochrome, YUV 4:2:0, 4:2:2, and 4:4:4 with AV1 chroma sample positioning; encode remains YUV 4:4:4 at this snapshot. Later high-bit-depth and SIMD paths must match it. |
| `Av1FrameBuffer` high-bit-depth sample layout and `Av1YuvConverter` 10/12-bit output conversion | AV1 section 6.4.1 bit depth and H.273 sample-range scaling | libaom `aom_scale/yv12config.h`, `av1/common/idct.c`, and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f`; libavif `src/avif.c` and `src/reformat.c` at `092276ce89098ead06db80975173191e5fee1826` | Establish two-byte native sample storage with sample-unit strides for 10/12-bit reconstruction and use the same scalar color model at every supported bit depth. |
| `Av1PredictionDecoder` and the scalar DC, directional, Paeth, smooth, and filter-intra predictors | AV1 sections 7.11.2 and 7.11.2.3 intra prediction | libaom `aom_dsp/intrapred.c` and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Behavioral oracle for neighbor addressing, directional upsampling, Paeth selection, smooth normalization, filter-intra taps, and chroma-from-luma row strides. Existing managed scalar predictors remain the implementation base. The WIP rectangular smooth digest expectations encode width/height-swapped weights and must be replaced only from an independently generated oracle, not regenerated from this implementation. |
| `Av1PredictionDecoder`, `Av1HighBitDepthPredictor`, and the scalar DC, directional, Paeth, smooth, and filter-intra predictors | AV1 sections 7.11.2 and 7.11.2.3 intra prediction | libaom `aom_dsp/intrapred.c` and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Behavioral oracle for neighbor addressing, directional upsampling, Paeth selection, smooth normalization, filter-intra taps, high-bit-depth clipping, and chroma-from-luma row strides. Existing managed scalar tables and predictors remain the implementation base. The WIP rectangular byte-pipeline smooth digest expectations encode width/height-swapped weights and must be replaced only from an independently generated oracle, not regenerated from this implementation. |
| `Av1Inverse2dTransformer` and `Av1InverseTransformerFactory` | AV1 section 7.11.2 inverse transform and reconstruction | libaom `av1/common/av1_inv_txfm1d.c`, `av1/common/av1_inv_txfm2d.c`, and `av1/common/idct.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar transform oracle for coefficient-row traversal, intermediate layout, stage ranges, clipping, and high-bit-depth sample addition. The managed 16-bit overload is also used as a parity oracle for the byte overload. |
This table is intentionally incomplete. Add a row before each additional AV1 or HEVC algorithm is ported or materially reshaped.

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

@ -18,7 +18,7 @@ internal static class Av1FilterIntraPredictor
// 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.
private static readonly sbyte[] Taps =
internal static readonly sbyte[] Taps =
[
// DC

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

@ -0,0 +1,423 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction;
/// <summary>
/// Implements AV1 intra prediction for 10-bit and 12-bit sample buffers.
/// </summary>
internal static class Av1HighBitDepthPredictor
{
private const int FilterBufferStride = 33;
private const int FilterBufferLength = FilterBufferStride * FilterBufferStride;
private const int FilterTapsPerPixel = 7;
private const int FilterPixelsPerGroup = 8;
private const int FilterTapsPerMode = FilterTapsPerPixel * FilterPixelsPerGroup;
internal static void DcPredictor(
bool hasLeft,
bool hasAbove,
Av1TransformSize transformSize,
Span<short> destination,
nuint destinationStride,
Span<short> above,
Span<short> left,
int bitDepth)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
int sum = 0;
int count = 0;
if (hasAbove)
{
for (int column = 0; column < width; column++)
{
sum += above[column];
}
count += width;
}
if (hasLeft)
{
for (int row = 0; row < height; row++)
{
sum += left[row];
}
count += height;
}
short prediction = count == 0
? (short)(1 << (bitDepth - 1))
: (short)((sum + (count >> 1)) / count);
Fill(destination, destinationStride, width, height, prediction);
}
internal static void GeneralPredictor(
Av1PredictionMode mode,
Av1TransformSize transformSize,
Span<short> destination,
nuint destinationStride,
Span<short> above,
Span<short> left)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
switch (mode)
{
case Av1PredictionMode.Horizontal:
PredictHorizontal(destination, destinationStride, left, width, height);
break;
case Av1PredictionMode.Vertical:
PredictVertical(destination, destinationStride, above, width, height);
break;
case Av1PredictionMode.Paeth:
PredictPaeth(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.Smooth:
PredictSmooth(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.SmoothHorizontal:
PredictSmoothHorizontal(destination, destinationStride, above, left, width, height);
break;
case Av1PredictionMode.SmoothVertical:
PredictSmoothVertical(destination, destinationStride, above, left, width, height);
break;
}
}
internal static void DirectionalPredictor(
Span<short> destination,
nuint destinationStride,
Av1TransformSize transformSize,
Span<short> above,
Span<short> left,
bool upsampleAbove,
bool upsampleLeft,
int angle,
int bitDepth)
{
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle));
int dx = Av1PredictorFactory.GetDeltaX(angle);
int dy = Av1PredictorFactory.GetDeltaY(angle);
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
if (angle is > 0 and < 90)
{
PredictDirectionalZone1(destination, destinationStride, above, upsampleAbove, dx, width, height, bitDepth);
}
else if (angle is > 90 and < 180)
{
PredictDirectionalZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, dx, dy, width, height, bitDepth);
}
else if (angle is > 180 and < 270)
{
PredictDirectionalZone3(destination, destinationStride, left, upsampleLeft, dx, dy, width, height, bitDepth);
}
else if (angle == 90)
{
PredictVertical(destination, destinationStride, above, width, height);
}
else if (angle == 180)
{
PredictHorizontal(destination, destinationStride, left, width, height);
}
}
internal static void FilterIntraPredictor(
Span<short> destination,
nuint destinationStride,
Av1TransformSize transformSize,
Span<short> above,
Span<short> left,
Av1FilterIntraMode mode,
int bitDepth)
{
int width = transformSize.GetWidth();
int height = transformSize.GetHeight();
int maximum = (1 << bitDepth) - 1;
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width));
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height));
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride));
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination));
Guard.MustBeSizedAtLeast(above, width, nameof(above));
Guard.MustBeSizedAtLeast(left, height, nameof(left));
Span<short> buffer = stackalloc short[FilterBufferLength];
ref short bufferRef = ref buffer[0];
ref short aboveRef = ref above[0];
ref short leftRef = ref left[0];
// Row zero includes the top-left sample followed by the top neighbors.
bufferRef = Unsafe.Subtract(ref aboveRef, 1);
above[..width].CopyTo(buffer[1..]);
for (int row = 0; row < height; row++)
{
Unsafe.Add(ref bufferRef, (row + 1) * FilterBufferStride) = Unsafe.Add(ref leftRef, row);
}
int modeOffset = (int)mode * FilterTapsPerMode;
ref sbyte tapsRef = ref Av1FilterIntraPredictor.Taps[modeOffset];
for (int row = 1; row <= height; row += 2)
{
for (int column = 1; column <= width; column += 4)
{
int sourceOffset = ((row - 1) * FilterBufferStride) + column - 1;
int p0 = Unsafe.Add(ref bufferRef, sourceOffset);
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1);
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2);
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3);
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4);
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + FilterBufferStride);
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * FilterBufferStride));
for (int pixel = 0; pixel < FilterPixelsPerGroup; pixel++)
{
int tapOffset = pixel * FilterTapsPerPixel;
int prediction =
(Unsafe.Add(ref tapsRef, tapOffset) * p0)
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1)
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2)
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3)
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4)
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5)
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6);
int rowOffset = pixel >> 2;
int columnOffset = pixel & 3;
int destinationOffset = ((row + rowOffset) * FilterBufferStride) + column + columnOffset;
Unsafe.Add(ref bufferRef, destinationOffset) =
(short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, maximum);
}
}
}
for (int row = 0; row < height; row++)
{
buffer.Slice(((row + 1) * FilterBufferStride) + 1, width).CopyTo(
destination.Slice(row * (int)destinationStride, width));
}
}
private static void PredictHorizontal(Span<short> destination, nuint stride, Span<short> left, int width, int height)
{
for (int row = 0; row < height; row++)
{
destination.Slice(row * (int)stride, width).Fill(left[row]);
}
}
private static void PredictVertical(Span<short> destination, nuint stride, Span<short> above, int width, int height)
{
for (int row = 0; row < height; row++)
{
above[..width].CopyTo(destination.Slice(row * (int)stride, width));
}
}
private static void PredictPaeth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int topLeft = Unsafe.Subtract(ref above[0], 1);
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
short leftValue = left[row];
short topValue = above[column];
int basis = topValue + leftValue - topLeft;
int leftDistance = Av1Math.AbsoluteDifference(basis, leftValue);
int topDistance = Av1Math.AbsoluteDifference(basis, topValue);
int topLeftDistance = Av1Math.AbsoluteDifference(basis, topLeft);
destinationRow[column] = leftDistance <= topDistance && leftDistance <= topLeftDistance
? leftValue
: topDistance <= topLeftDistance ? topValue : (short)topLeft;
}
}
}
private static void PredictSmooth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int below = left[height - 1];
int right = above[width - 1];
Span<int> widthWeights = Av1SmoothPredictor.Weights.AsSpan(width, width);
Span<int> heightWeights = Av1SmoothPredictor.Weights.AsSpan(height, height);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
int log2Scale = Av1SmoothPredictor.WeightLog2Scale + 1;
for (int row = 0; row < height; row++)
{
int rowWeight = heightWeights[row];
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int columnWeight = widthWeights[column];
int prediction = (above[column] * rowWeight) + (below * (scale - rowWeight));
prediction += (left[row] * columnWeight) + (right * (scale - columnWeight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, log2Scale);
}
}
}
private static void PredictSmoothHorizontal(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int right = above[width - 1];
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(width, width);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int weight = weights[column];
int prediction = (left[row] * weight) + (right * (scale - weight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale);
}
}
}
private static void PredictSmoothVertical(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height)
{
int below = left[height - 1];
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(height, height);
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale;
for (int row = 0; row < height; row++)
{
int weight = weights[row];
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
for (int column = 0; column < width; column++)
{
int prediction = (above[column] * weight) + (below * (scale - weight));
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale);
}
}
}
private static void PredictDirectionalZone1(Span<short> destination, nuint stride, Span<short> above, bool upsample, int dx, int width, int height, int bitDepth)
{
int upsampleAbove = upsample ? 1 : 0;
int maxBasisX = (width + height - 1) << upsampleAbove;
int fractionBitCount = 6 - upsampleAbove;
int basisIncrement = 1 << upsampleAbove;
int maximum = (1 << bitDepth) - 1;
int x = dx;
ref short aboveRef = ref above[0];
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
int basis = x >> fractionBitCount;
int shift = ((x << upsampleAbove) & 0x3F) >> 1;
for (int column = 0; column < width; column++)
{
if (basis < maxBasisX)
{
int prediction = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift);
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
else
{
destinationRow[column] = Unsafe.Add(ref aboveRef, maxBasisX);
}
basis += basisIncrement;
}
x += dx;
}
}
private static void PredictDirectionalZone2(Span<short> destination, nuint stride, Span<short> above, Span<short> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy, int width, int height, int bitDepth)
{
int upsampleAbove = doUpsampleAbove ? 1 : 0;
int upsampleLeft = doUpsampleLeft ? 1 : 0;
int minBasisX = -(1 << upsampleAbove);
int fractionBitCountX = 6 - upsampleAbove;
int fractionBitCountY = 6 - upsampleLeft;
int basisIncrementX = 1 << upsampleAbove;
int maximum = (1 << bitDepth) - 1;
int x = -dx;
ref short aboveRef = ref above[0];
ref short leftRef = ref left[0];
for (int row = 0; row < height; row++)
{
Span<short> destinationRow = destination.Slice(row * (int)stride, width);
int basisX = x >> fractionBitCountX;
int y = (row << 6) - dy;
for (int column = 0; column < width; column++, basisX += basisIncrementX, y -= dy)
{
int prediction;
if (basisX >= minBasisX)
{
int shift = ((x * (1 << upsampleAbove)) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref aboveRef, basisX) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basisX + 1) * shift);
}
else
{
int basisY = y >> fractionBitCountY;
int shift = ((y * (1 << upsampleLeft)) & 0x3F) >> 1;
prediction = (Unsafe.Add(ref leftRef, basisY) * (32 - shift)) + (Unsafe.Add(ref leftRef, basisY + 1) * shift);
}
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
x -= dx;
}
}
private static void PredictDirectionalZone3(Span<short> destination, nuint stride, Span<short> left, bool upsample, int dx, int dy, int width, int height, int bitDepth)
{
int upsampleLeft = upsample ? 1 : 0;
int maxBasisY = (width + height - 1) << upsampleLeft;
int fractionBitCount = 6 - upsampleLeft;
int basisIncrement = 1 << upsampleLeft;
int maximum = (1 << bitDepth) - 1;
int y = dy;
ref short leftRef = ref left[0];
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction.");
for (int column = 0; column < width; column++)
{
int basis = y >> fractionBitCount;
int shift = ((y << upsampleLeft) & 0x3F) >> 1;
for (int row = 0; row < height; row++)
{
int destinationOffset = (row * (int)stride) + column;
if (basis < maxBasisY)
{
int prediction = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift);
destination[destinationOffset] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum);
}
else
{
destination[destinationOffset] = Unsafe.Add(ref leftRef, maxBasisY);
}
basis += basisIncrement;
}
y += dy;
}
}
private static void Fill(Span<short> destination, nuint stride, int width, int height, short value)
{
for (int row = 0; row < height; row++)
{
destination.Slice(row * (int)stride, width).Fill(value);
}
}
}

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

@ -69,6 +69,9 @@ 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)
=> Av1HighBitDepthPredictor.DcPredictor(hasLeft, hasAbove, transformSize, destination, destinationStride, aboveRow, leftColumn, bitDepth);
/// <summary>
/// SVT: svt_aom_highbd_dr_predictor
/// </summary>
@ -102,9 +105,15 @@ 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)
=> 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)
=> 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)
=> 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)
{
switch (mode)
@ -130,11 +139,14 @@ internal class Av1PredictorFactory
}
}
internal 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));
private static int GetDeltaY(int angle)
internal static int GetDeltaY(int angle)
{
if (angle is > 90 and < 180)
{
@ -155,7 +167,7 @@ internal class Av1PredictorFactory
// 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)
internal static int GetDeltaX(int angle)
{
if (angle is > 0 and < 90)
{

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

@ -413,6 +413,137 @@ public class Av1PredictorTests
Assert.All(destination, value => Assert.Equal(73, value));
}
[Theory]
[InlineData(true, true, 800)]
[InlineData(true, false, 700)]
[InlineData(false, true, 900)]
[InlineData(false, false, 512)]
public void HighBitDepthDcPredictionUsesAvailableEdges(bool hasLeft, bool hasAbove, short expected)
{
short[] destination = new short[16];
short[] above = [900, 900, 900, 900];
short[] left = [700, 700, 700, 700];
Av1PredictorFactory.DcPredictor(
hasLeft,
hasAbove,
Av1TransformSize.Size4x4,
destination,
4,
above,
left,
10);
Assert.All(destination, value => Assert.Equal(expected, value));
}
[Theory]
[InlineData((int)Av1PredictionMode.Horizontal)]
[InlineData((int)Av1PredictionMode.Vertical)]
[InlineData((int)Av1PredictionMode.Paeth)]
[InlineData((int)Av1PredictionMode.Smooth)]
[InlineData((int)Av1PredictionMode.SmoothHorizontal)]
[InlineData((int)Av1PredictionMode.SmoothVertical)]
public void HighBitDepthGeneralPredictionMatchesTranslatedEightBitOracle(int modeIndex)
{
const int offset = 512;
Av1PredictionMode mode = (Av1PredictionMode)modeIndex;
byte[] aboveData = [50, 60, 10, 90, 40];
byte[] left = [20, 80, 30, 100];
short[] highAboveData = aboveData.Select(value => (short)(value + offset)).ToArray();
short[] highLeft = left.Select(value => (short)(value + offset)).ToArray();
byte[] expected = new byte[16];
short[] actual = new short[16];
Av1PredictorFactory.GeneralPredictor(
mode,
Av1TransformSize.Size4x4,
expected,
4,
aboveData.AsSpan(1),
left);
Av1PredictorFactory.GeneralPredictor(
mode,
Av1TransformSize.Size4x4,
actual,
4,
highAboveData.AsSpan(1),
highLeft);
Assert.Equal(expected.Select(value => (short)(value + offset)), actual);
}
[Theory]
[InlineData(45)]
[InlineData(67)]
[InlineData(90)]
[InlineData(113)]
[InlineData(135)]
[InlineData(157)]
[InlineData(180)]
[InlineData(203)]
public void HighBitDepthDirectionalPredictionMatchesTranslatedEightBitOracle(int angle)
{
const int offset = 512;
byte[] aboveData = new byte[256];
byte[] leftData = new byte[256];
for (int i = 0; i < aboveData.Length; i++)
{
aboveData[i] = (byte)(20 + (i % 180));
leftData[i] = (byte)(30 + (i % 170));
}
short[] highAboveData = aboveData.Select(value => (short)(value + offset)).ToArray();
short[] highLeftData = leftData.Select(value => (short)(value + offset)).ToArray();
byte[] expected = new byte[16];
short[] actual = new short[16];
Av1PredictorFactory.DirectionalPredictor(
expected,
4,
Av1TransformSize.Size4x4,
aboveData.AsSpan(128),
leftData.AsSpan(128),
false,
false,
angle);
Av1PredictorFactory.DirectionalPredictor(
actual,
4,
Av1TransformSize.Size4x4,
highAboveData.AsSpan(128),
highLeftData.AsSpan(128),
false,
false,
angle,
10);
Assert.Equal(expected.Select(value => (short)(value + offset)), actual);
}
[Theory]
[MemberData(nameof(GetFilterIntraPredictions))]
public void HighBitDepthFilterIntraMatchesTranslatedLibaomVector(int mode, byte[] expected)
{
const int offset = 512;
short[] destination = new short[16];
short[] aboveData = [529, 542, 582, 622, 662];
short[] left = [552, 592, 632, 672];
Av1PredictorFactory.FilterIntraPredictor(
destination,
4,
Av1TransformSize.Size4x4,
aboveData.AsSpan(1),
left,
(Av1FilterIntraMode)mode,
10);
Assert.Equal(expected.Select(value => (short)(value + offset)), destination);
}
private static void AssertValue(byte expected, byte actual)
{
Assert.NotEqual(0, actual);

Loading…
Cancel
Save