mirror of https://github.com/SixLabors/ImageSharp
15 changed files with 1397 additions and 20 deletions
@ -0,0 +1,560 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Reconstructs local and global affine warped-motion prediction blocks.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The number of rows in one warped filter's horizontal intermediate tile.
|
|||
/// </summary>
|
|||
private const int WarpedIntermediateRows = 15; |
|||
|
|||
/// <summary>
|
|||
/// The number of columns in one warped filter tile.
|
|||
/// </summary>
|
|||
private const int WarpedTileSize = 8; |
|||
|
|||
/// <summary>
|
|||
/// The number of low model bits removed when addressing the warped filter table.
|
|||
/// </summary>
|
|||
private const int WarpedDifferencePrecisionBits = 10; |
|||
|
|||
/// <summary>
|
|||
/// The number of fractional positions in one warped pixel.
|
|||
/// </summary>
|
|||
private const int WarpedPixelPrecisionShifts = 64; |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of signed 16-bit elements required by warped prediction.
|
|||
/// </summary>
|
|||
public const int WarpedScratchLength = WarpedIntermediateRows * WarpedTileSize; |
|||
|
|||
/// <summary>
|
|||
/// Supplies the horizontal and vertical eight-tap dot products for one execution width.
|
|||
/// </summary>
|
|||
private interface IWarpedConvolution |
|||
{ |
|||
/// <summary>
|
|||
/// Convolves eight adjacent unsigned byte samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int Convolve(ref byte source, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves eight adjacent unsigned high-bit-depth samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int Convolve(ref ushort source, ref short coefficients); |
|||
|
|||
/// <summary>
|
|||
/// Convolves eight vertically strided unsigned intermediate samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first intermediate sample.</param>
|
|||
/// <param name="stride">The distance between intermediate rows.</param>
|
|||
/// <param name="coefficients">The first signed Q7 coefficient.</param>
|
|||
/// <returns>The sum of the eight sample-coefficient products.</returns>
|
|||
public static abstract int ConvolveVertical(ref ushort source, int stride, ref short coefficients); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped prediction using the widest supported convolution operator.
|
|||
/// </summary>
|
|||
public static void PredictWarped( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictWarped<WarpedVector128Convolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth affine warped prediction using the widest supported convolution operator.
|
|||
/// </summary>
|
|||
public static void PredictWarped( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
PredictWarped<WarpedVector128Convolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
|
|||
return; |
|||
} |
|||
|
|||
PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs an 8-bit affine warped prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictWarpedScalar( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
=> PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
parameters, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a high-bit-depth affine warped prediction without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void PredictWarpedScalar( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
=> PredictWarped<WarpedScalarConvolution>( |
|||
source, |
|||
sourceStride, |
|||
sourceOrigin, |
|||
sourceWidth, |
|||
sourceHeight, |
|||
destination, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
bitDepth, |
|||
parameters, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one 8-bit warped block through a closed convolution operator.
|
|||
/// </summary>
|
|||
private static void PredictWarped<TConvolution>( |
|||
ReadOnlySpan<byte> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
where TConvolution : struct, IWarpedConvolution |
|||
{ |
|||
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Span<ushort> intermediate = MemoryMarshal.Cast<short, ushort>(scratch)[..WarpedScratchLength]; |
|||
int horizontalBias = 1 << (8 + FilterBits - 1); |
|||
int verticalBias = 1 << (8 + (2 * FilterBits) - Round0Bits); |
|||
int verticalRound = (2 * FilterBits) - Round0Bits; |
|||
|
|||
for (int tileRow = destinationPosition.Y; tileRow < destinationPosition.Y + height; tileRow += WarpedTileSize) |
|||
{ |
|||
for (int tileColumn = destinationPosition.X; tileColumn < destinationPosition.X + width; tileColumn += WarpedTileSize) |
|||
{ |
|||
DeriveWarpedTilePosition( |
|||
parameters, |
|||
tileColumn, |
|||
tileRow, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY); |
|||
|
|||
for (int row = -7; row < 8; row++) |
|||
{ |
|||
int sourceY = Math.Clamp(integerY + row, 0, sourceHeight - 1); |
|||
int phase = phaseX + (parameters.Beta * (row + 4)); |
|||
for (int column = -4; column < 4; column++) |
|||
{ |
|||
int sourceX = integerX + column - 3; |
|||
int sourceIndex = ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sourceX; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = horizontalBias + TConvolution.Convolve(ref Unsafe.Add(ref sourceBase, sourceIndex), ref coefficients); |
|||
intermediate[((row + 7) * WarpedTileSize) + column + 4] = (ushort)RoundPowerOfTwoScalar(sum, Round0Bits); |
|||
phase += parameters.Alpha; |
|||
} |
|||
} |
|||
|
|||
int tileHeight = Math.Min(WarpedTileSize, destinationPosition.Y + height - tileRow); |
|||
int tileWidth = Math.Min(WarpedTileSize, destinationPosition.X + width - tileColumn); |
|||
for (int row = 0; row < tileHeight; row++) |
|||
{ |
|||
int phase = phaseY + (parameters.Delta * row); |
|||
int destinationRowOffset = (tileRow - destinationPosition.Y + row) * destinationStride; |
|||
for (int column = 0; column < tileWidth; column++) |
|||
{ |
|||
ref ushort intermediateSource = ref intermediate[(row * WarpedTileSize) + column]; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = verticalBias + TConvolution.ConvolveVertical(ref intermediateSource, WarpedTileSize, ref coefficients); |
|||
int value = RoundPowerOfTwoScalar(sum, verticalRound) - (1 << 7) - (1 << 8); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + tileColumn - destinationPosition.X + column) = |
|||
(byte)Math.Clamp(value, byte.MinValue, byte.MaxValue); |
|||
|
|||
phase += parameters.Gamma; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one high-bit-depth warped block through a closed convolution operator.
|
|||
/// </summary>
|
|||
private static void PredictWarped<TConvolution>( |
|||
ReadOnlySpan<ushort> source, |
|||
int sourceStride, |
|||
Point sourceOrigin, |
|||
int sourceWidth, |
|||
int sourceHeight, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
Point destinationPosition, |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int bitDepth, |
|||
Av1GlobalMotionParameters parameters, |
|||
Span<short> scratch) |
|||
where TConvolution : struct, IWarpedConvolution |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Span<ushort> intermediate = MemoryMarshal.Cast<short, ushort>(scratch)[..WarpedScratchLength]; |
|||
|
|||
// Twelve-bit prediction increases round0 by two so the biased horizontal intermediate remains representable
|
|||
// in sixteen bits. Reducing round1 by the same amount preserves the complete normative Q14 shift.
|
|||
int intermediateRange = bitDepth + FilterBits - Round0Bits + 2; |
|||
int round0 = Round0Bits + Math.Max(intermediateRange - 16, 0); |
|||
int verticalRound = (2 * FilterBits) - round0; |
|||
int horizontalBias = 1 << (bitDepth + FilterBits - 1); |
|||
int verticalBias = 1 << (bitDepth + (2 * FilterBits) - round0); |
|||
int maximum = (1 << bitDepth) - 1; |
|||
|
|||
for (int tileRow = destinationPosition.Y; tileRow < destinationPosition.Y + height; tileRow += WarpedTileSize) |
|||
{ |
|||
for (int tileColumn = destinationPosition.X; tileColumn < destinationPosition.X + width; tileColumn += WarpedTileSize) |
|||
{ |
|||
DeriveWarpedTilePosition( |
|||
parameters, |
|||
tileColumn, |
|||
tileRow, |
|||
subsamplingX, |
|||
subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY); |
|||
|
|||
for (int row = -7; row < 8; row++) |
|||
{ |
|||
int sourceY = Math.Clamp(integerY + row, 0, sourceHeight - 1); |
|||
int phase = phaseX + (parameters.Beta * (row + 4)); |
|||
for (int column = -4; column < 4; column++) |
|||
{ |
|||
int sourceX = integerX + column - 3; |
|||
int sourceIndex = ((sourceOrigin.Y + sourceY) * sourceStride) + sourceOrigin.X + sourceX; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = horizontalBias + TConvolution.Convolve(ref Unsafe.Add(ref sourceBase, sourceIndex), ref coefficients); |
|||
intermediate[((row + 7) * WarpedTileSize) + column + 4] = (ushort)RoundPowerOfTwoScalar(sum, round0); |
|||
phase += parameters.Alpha; |
|||
} |
|||
} |
|||
|
|||
int tileHeight = Math.Min(WarpedTileSize, destinationPosition.Y + height - tileRow); |
|||
int tileWidth = Math.Min(WarpedTileSize, destinationPosition.X + width - tileColumn); |
|||
for (int row = 0; row < tileHeight; row++) |
|||
{ |
|||
int phase = phaseY + (parameters.Delta * row); |
|||
int destinationRowOffset = (tileRow - destinationPosition.Y + row) * destinationStride; |
|||
for (int column = 0; column < tileWidth; column++) |
|||
{ |
|||
ref ushort intermediateSource = ref intermediate[(row * WarpedTileSize) + column]; |
|||
ref short coefficients = ref GetWarpedFilterReference(phase); |
|||
int sum = verticalBias + TConvolution.ConvolveVertical(ref intermediateSource, WarpedTileSize, ref coefficients); |
|||
int value = RoundPowerOfTwoScalar(sum, verticalRound) - (1 << (bitDepth - 1)) - (1 << bitDepth); |
|||
Unsafe.Add(ref destinationBase, destinationRowOffset + tileColumn - destinationPosition.X + column) = |
|||
(ushort)Math.Clamp(value, 0, maximum); |
|||
|
|||
phase += parameters.Gamma; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects the center of one 8x8 output tile and derives its integer source position and reduced phases.
|
|||
/// </summary>
|
|||
private static void DeriveWarpedTilePosition( |
|||
Av1GlobalMotionParameters parameters, |
|||
int tileColumn, |
|||
int tileRow, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
out int integerX, |
|||
out int integerY, |
|||
out int phaseX, |
|||
out int phaseY) |
|||
{ |
|||
int sourceX = (tileColumn + 4) << subsamplingX; |
|||
int sourceY = (tileRow + 4) << subsamplingY; |
|||
long projectedX = ((long)parameters[2] * sourceX) + ((long)parameters[3] * sourceY) + parameters[0]; |
|||
long projectedY = ((long)parameters[4] * sourceX) + ((long)parameters[5] * sourceY) + parameters[1]; |
|||
long planeX = projectedX >> subsamplingX; |
|||
long planeY = projectedY >> subsamplingY; |
|||
integerX = (int)(planeX >> Av1GlobalMotionParameters.ModelPrecisionBits); |
|||
integerY = (int)(planeY >> Av1GlobalMotionParameters.ModelPrecisionBits); |
|||
phaseX = (int)planeX & (Av1GlobalMotionParameters.ModelScale - 1); |
|||
phaseY = (int)planeY & (Av1GlobalMotionParameters.ModelScale - 1); |
|||
phaseX += (-4 * parameters.Alpha) + (-4 * parameters.Beta); |
|||
phaseY += (-4 * parameters.Gamma) + (-4 * parameters.Delta); |
|||
|
|||
// Shear parameters are quantized to 64-model-unit steps. Clearing the same low bits after the tile-center
|
|||
// projection keeps negative and positive phases on the exact filter-table grid used by the bitstream model.
|
|||
phaseX &= -1 << 6; |
|||
phaseY &= -1 << 6; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a reference to the first coefficient for one reduced warped-filter phase.
|
|||
/// </summary>
|
|||
private static ref short GetWarpedFilterReference(int phase) |
|||
{ |
|||
int filterIndex = ((phase + (1 << (WarpedDifferencePrecisionBits - 1))) >> WarpedDifferencePrecisionBits) + |
|||
WarpedPixelPrecisionShifts; |
|||
|
|||
return ref Unsafe.Add(ref MemoryMarshal.GetReference(WarpedFilter), filterIndex * FilterCoefficientCount); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative value by a power of two with nearest-integer rounding.
|
|||
/// </summary>
|
|||
private static int RoundPowerOfTwoScalar(int value, int bitCount) |
|||
=> (value + (1 << (bitCount - 1))) >> bitCount; |
|||
|
|||
/// <summary>
|
|||
/// Executes warped dot products with portable 128-bit SIMD.
|
|||
/// </summary>
|
|||
private readonly struct WarpedVector128Convolution : IWarpedConvolution |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref byte source, ref short coefficients) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source); |
|||
(Vector128<ushort> samples, _) = Vector128.Widen(packed); |
|||
return MultiplyAndSum(samples, Vector128.LoadUnsafe(ref coefficients)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref ushort source, ref short coefficients) |
|||
=> MultiplyAndSum(Vector128.LoadUnsafe(ref source), Vector128.LoadUnsafe(ref coefficients)); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int ConvolveVertical(ref ushort source, int stride, ref short coefficients) |
|||
{ |
|||
Vector128<ushort> samples = Vector128.Create( |
|||
source, |
|||
Unsafe.Add(ref source, stride), |
|||
Unsafe.Add(ref source, stride * 2), |
|||
Unsafe.Add(ref source, stride * 3), |
|||
Unsafe.Add(ref source, stride * 4), |
|||
Unsafe.Add(ref source, stride * 5), |
|||
Unsafe.Add(ref source, stride * 6), |
|||
Unsafe.Add(ref source, stride * 7)); |
|||
|
|||
return MultiplyAndSum(samples, Vector128.LoadUnsafe(ref coefficients)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens unsigned samples and signed coefficients before accumulating their exact 32-bit products.
|
|||
/// </summary>
|
|||
private static int MultiplyAndSum(Vector128<ushort> samples, Vector128<short> coefficients) |
|||
{ |
|||
(Vector128<uint> sampleLower, Vector128<uint> sampleUpper) = Vector128.Widen(samples); |
|||
(Vector128<int> coefficientLower, Vector128<int> coefficientUpper) = Vector128.Widen(coefficients); |
|||
return Vector128.Sum(sampleLower.AsInt32() * coefficientLower) + |
|||
Vector128.Sum(sampleUpper.AsInt32() * coefficientUpper); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Executes warped dot products without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
private readonly struct WarpedScalarConvolution : IWarpedConvolution |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref byte source, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (nuint index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Convolve(ref ushort source, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (nuint index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int ConvolveVertical(ref ushort source, int stride, ref short coefficients) |
|||
{ |
|||
int sum = 0; |
|||
for (int index = 0; index < FilterCoefficientCount; index++) |
|||
{ |
|||
sum += Unsafe.Add(ref source, index * stride) * Unsafe.Add(ref coefficients, index); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,210 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Provides the normative Q7 filter kernels for affine warped-motion prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1InterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the 193 consecutive eight-tap warped-filter phases spanning fractional positions [-1, 2].
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<short> WarpedFilter => |
|||
[ |
|||
0, 0, 127, 1, 0, 0, 0, 0, |
|||
0, -1, 127, 2, 0, 0, 0, 0, |
|||
1, -3, 127, 4, -1, 0, 0, 0, |
|||
1, -4, 126, 6, -2, 1, 0, 0, |
|||
1, -5, 126, 8, -3, 1, 0, 0, |
|||
1, -6, 125, 11, -4, 1, 0, 0, |
|||
1, -7, 124, 13, -4, 1, 0, 0, |
|||
2, -8, 123, 15, -5, 1, 0, 0, |
|||
2, -9, 122, 18, -6, 1, 0, 0, |
|||
2, -10, 121, 20, -6, 1, 0, 0, |
|||
2, -11, 120, 22, -7, 2, 0, 0, |
|||
2, -12, 119, 25, -8, 2, 0, 0, |
|||
3, -13, 117, 27, -8, 2, 0, 0, |
|||
3, -13, 116, 29, -9, 2, 0, 0, |
|||
3, -14, 114, 32, -10, 3, 0, 0, |
|||
3, -15, 113, 35, -10, 2, 0, 0, |
|||
3, -15, 111, 37, -11, 3, 0, 0, |
|||
3, -16, 109, 40, -11, 3, 0, 0, |
|||
3, -16, 108, 42, -12, 3, 0, 0, |
|||
4, -17, 106, 45, -13, 3, 0, 0, |
|||
4, -17, 104, 47, -13, 3, 0, 0, |
|||
4, -17, 102, 50, -14, 3, 0, 0, |
|||
4, -17, 100, 52, -14, 3, 0, 0, |
|||
4, -18, 98, 55, -15, 4, 0, 0, |
|||
4, -18, 96, 58, -15, 3, 0, 0, |
|||
4, -18, 94, 60, -16, 4, 0, 0, |
|||
4, -18, 91, 63, -16, 4, 0, 0, |
|||
4, -18, 89, 65, -16, 4, 0, 0, |
|||
4, -18, 87, 68, -17, 4, 0, 0, |
|||
4, -18, 85, 70, -17, 4, 0, 0, |
|||
4, -18, 82, 73, -17, 4, 0, 0, |
|||
4, -18, 80, 75, -17, 4, 0, 0, |
|||
4, -18, 78, 78, -18, 4, 0, 0, |
|||
4, -17, 75, 80, -18, 4, 0, 0, |
|||
4, -17, 73, 82, -18, 4, 0, 0, |
|||
4, -17, 70, 85, -18, 4, 0, 0, |
|||
4, -17, 68, 87, -18, 4, 0, 0, |
|||
4, -16, 65, 89, -18, 4, 0, 0, |
|||
4, -16, 63, 91, -18, 4, 0, 0, |
|||
4, -16, 60, 94, -18, 4, 0, 0, |
|||
3, -15, 58, 96, -18, 4, 0, 0, |
|||
4, -15, 55, 98, -18, 4, 0, 0, |
|||
3, -14, 52, 100, -17, 4, 0, 0, |
|||
3, -14, 50, 102, -17, 4, 0, 0, |
|||
3, -13, 47, 104, -17, 4, 0, 0, |
|||
3, -13, 45, 106, -17, 4, 0, 0, |
|||
3, -12, 42, 108, -16, 3, 0, 0, |
|||
3, -11, 40, 109, -16, 3, 0, 0, |
|||
3, -11, 37, 111, -15, 3, 0, 0, |
|||
2, -10, 35, 113, -15, 3, 0, 0, |
|||
3, -10, 32, 114, -14, 3, 0, 0, |
|||
2, -9, 29, 116, -13, 3, 0, 0, |
|||
2, -8, 27, 117, -13, 3, 0, 0, |
|||
2, -8, 25, 119, -12, 2, 0, 0, |
|||
2, -7, 22, 120, -11, 2, 0, 0, |
|||
1, -6, 20, 121, -10, 2, 0, 0, |
|||
1, -6, 18, 122, -9, 2, 0, 0, |
|||
1, -5, 15, 123, -8, 2, 0, 0, |
|||
1, -4, 13, 124, -7, 1, 0, 0, |
|||
1, -4, 11, 125, -6, 1, 0, 0, |
|||
1, -3, 8, 126, -5, 1, 0, 0, |
|||
1, -2, 6, 126, -4, 1, 0, 0, |
|||
0, -1, 4, 127, -3, 1, 0, 0, |
|||
0, 0, 2, 127, -1, 0, 0, 0, |
|||
0, 0, 0, 127, 1, 0, 0, 0, |
|||
0, 0, -1, 127, 2, 0, 0, 0, |
|||
0, 1, -3, 127, 4, -2, 1, 0, |
|||
0, 1, -5, 127, 6, -2, 1, 0, |
|||
0, 2, -6, 126, 8, -3, 1, 0, |
|||
-1, 2, -7, 126, 11, -4, 2, -1, |
|||
-1, 3, -8, 125, 13, -5, 2, -1, |
|||
-1, 3, -10, 124, 16, -6, 3, -1, |
|||
-1, 4, -11, 123, 18, -7, 3, -1, |
|||
-1, 4, -12, 122, 20, -7, 3, -1, |
|||
-1, 4, -13, 121, 23, -8, 3, -1, |
|||
-2, 5, -14, 120, 25, -9, 4, -1, |
|||
-1, 5, -15, 119, 27, -10, 4, -1, |
|||
-1, 5, -16, 118, 30, -11, 4, -1, |
|||
-2, 6, -17, 116, 33, -12, 5, -1, |
|||
-2, 6, -17, 114, 35, -12, 5, -1, |
|||
-2, 6, -18, 113, 38, -13, 5, -1, |
|||
-2, 7, -19, 111, 41, -14, 6, -2, |
|||
-2, 7, -19, 110, 43, -15, 6, -2, |
|||
-2, 7, -20, 108, 46, -15, 6, -2, |
|||
-2, 7, -20, 106, 49, -16, 6, -2, |
|||
-2, 7, -21, 104, 51, -16, 7, -2, |
|||
-2, 7, -21, 102, 54, -17, 7, -2, |
|||
-2, 8, -21, 100, 56, -18, 7, -2, |
|||
-2, 8, -22, 98, 59, -18, 7, -2, |
|||
-2, 8, -22, 96, 62, -19, 7, -2, |
|||
-2, 8, -22, 94, 64, -19, 7, -2, |
|||
-2, 8, -22, 91, 67, -20, 8, -2, |
|||
-2, 8, -22, 89, 69, -20, 8, -2, |
|||
-2, 8, -22, 87, 72, -21, 8, -2, |
|||
-2, 8, -21, 84, 74, -21, 8, -2, |
|||
-2, 8, -22, 82, 77, -21, 8, -2, |
|||
-2, 8, -21, 79, 79, -21, 8, -2, |
|||
-2, 8, -21, 77, 82, -22, 8, -2, |
|||
-2, 8, -21, 74, 84, -21, 8, -2, |
|||
-2, 8, -21, 72, 87, -22, 8, -2, |
|||
-2, 8, -20, 69, 89, -22, 8, -2, |
|||
-2, 8, -20, 67, 91, -22, 8, -2, |
|||
-2, 7, -19, 64, 94, -22, 8, -2, |
|||
-2, 7, -19, 62, 96, -22, 8, -2, |
|||
-2, 7, -18, 59, 98, -22, 8, -2, |
|||
-2, 7, -18, 56, 100, -21, 8, -2, |
|||
-2, 7, -17, 54, 102, -21, 7, -2, |
|||
-2, 7, -16, 51, 104, -21, 7, -2, |
|||
-2, 6, -16, 49, 106, -20, 7, -2, |
|||
-2, 6, -15, 46, 108, -20, 7, -2, |
|||
-2, 6, -15, 43, 110, -19, 7, -2, |
|||
-2, 6, -14, 41, 111, -19, 7, -2, |
|||
-1, 5, -13, 38, 113, -18, 6, -2, |
|||
-1, 5, -12, 35, 114, -17, 6, -2, |
|||
-1, 5, -12, 33, 116, -17, 6, -2, |
|||
-1, 4, -11, 30, 118, -16, 5, -1, |
|||
-1, 4, -10, 27, 119, -15, 5, -1, |
|||
-1, 4, -9, 25, 120, -14, 5, -2, |
|||
-1, 3, -8, 23, 121, -13, 4, -1, |
|||
-1, 3, -7, 20, 122, -12, 4, -1, |
|||
-1, 3, -7, 18, 123, -11, 4, -1, |
|||
-1, 3, -6, 16, 124, -10, 3, -1, |
|||
-1, 2, -5, 13, 125, -8, 3, -1, |
|||
-1, 2, -4, 11, 126, -7, 2, -1, |
|||
0, 1, -3, 8, 126, -6, 2, 0, |
|||
0, 1, -2, 6, 127, -5, 1, 0, |
|||
0, 1, -2, 4, 127, -3, 1, 0, |
|||
0, 0, 0, 2, 127, -1, 0, 0, |
|||
0, 0, 0, 1, 127, 0, 0, 0, |
|||
0, 0, 0, -1, 127, 2, 0, 0, |
|||
0, 0, 1, -3, 127, 4, -1, 0, |
|||
0, 0, 1, -4, 126, 6, -2, 1, |
|||
0, 0, 1, -5, 126, 8, -3, 1, |
|||
0, 0, 1, -6, 125, 11, -4, 1, |
|||
0, 0, 1, -7, 124, 13, -4, 1, |
|||
0, 0, 2, -8, 123, 15, -5, 1, |
|||
0, 0, 2, -9, 122, 18, -6, 1, |
|||
0, 0, 2, -10, 121, 20, -6, 1, |
|||
0, 0, 2, -11, 120, 22, -7, 2, |
|||
0, 0, 2, -12, 119, 25, -8, 2, |
|||
0, 0, 3, -13, 117, 27, -8, 2, |
|||
0, 0, 3, -13, 116, 29, -9, 2, |
|||
0, 0, 3, -14, 114, 32, -10, 3, |
|||
0, 0, 3, -15, 113, 35, -10, 2, |
|||
0, 0, 3, -15, 111, 37, -11, 3, |
|||
0, 0, 3, -16, 109, 40, -11, 3, |
|||
0, 0, 3, -16, 108, 42, -12, 3, |
|||
0, 0, 4, -17, 106, 45, -13, 3, |
|||
0, 0, 4, -17, 104, 47, -13, 3, |
|||
0, 0, 4, -17, 102, 50, -14, 3, |
|||
0, 0, 4, -17, 100, 52, -14, 3, |
|||
0, 0, 4, -18, 98, 55, -15, 4, |
|||
0, 0, 4, -18, 96, 58, -15, 3, |
|||
0, 0, 4, -18, 94, 60, -16, 4, |
|||
0, 0, 4, -18, 91, 63, -16, 4, |
|||
0, 0, 4, -18, 89, 65, -16, 4, |
|||
0, 0, 4, -18, 87, 68, -17, 4, |
|||
0, 0, 4, -18, 85, 70, -17, 4, |
|||
0, 0, 4, -18, 82, 73, -17, 4, |
|||
0, 0, 4, -18, 80, 75, -17, 4, |
|||
0, 0, 4, -18, 78, 78, -18, 4, |
|||
0, 0, 4, -17, 75, 80, -18, 4, |
|||
0, 0, 4, -17, 73, 82, -18, 4, |
|||
0, 0, 4, -17, 70, 85, -18, 4, |
|||
0, 0, 4, -17, 68, 87, -18, 4, |
|||
0, 0, 4, -16, 65, 89, -18, 4, |
|||
0, 0, 4, -16, 63, 91, -18, 4, |
|||
0, 0, 4, -16, 60, 94, -18, 4, |
|||
0, 0, 3, -15, 58, 96, -18, 4, |
|||
0, 0, 4, -15, 55, 98, -18, 4, |
|||
0, 0, 3, -14, 52, 100, -17, 4, |
|||
0, 0, 3, -14, 50, 102, -17, 4, |
|||
0, 0, 3, -13, 47, 104, -17, 4, |
|||
0, 0, 3, -13, 45, 106, -17, 4, |
|||
0, 0, 3, -12, 42, 108, -16, 3, |
|||
0, 0, 3, -11, 40, 109, -16, 3, |
|||
0, 0, 3, -11, 37, 111, -15, 3, |
|||
0, 0, 2, -10, 35, 113, -15, 3, |
|||
0, 0, 3, -10, 32, 114, -14, 3, |
|||
0, 0, 2, -9, 29, 116, -13, 3, |
|||
0, 0, 2, -8, 27, 117, -13, 3, |
|||
0, 0, 2, -8, 25, 119, -12, 2, |
|||
0, 0, 2, -7, 22, 120, -11, 2, |
|||
0, 0, 1, -6, 20, 121, -10, 2, |
|||
0, 0, 1, -6, 18, 122, -9, 2, |
|||
0, 0, 1, -5, 15, 123, -8, 2, |
|||
0, 0, 1, -4, 13, 124, -7, 1, |
|||
0, 0, 1, -4, 11, 125, -6, 1, |
|||
0, 0, 1, -3, 8, 126, -5, 1, |
|||
0, 0, 1, -2, 6, 126, -4, 1, |
|||
0, 0, 0, -1, 4, 127, -3, 1, |
|||
0, 0, 0, 0, 2, 127, -1, 0, |
|||
0, 0, 0, 0, 2, 127, -1, 0, |
|||
]; |
|||
} |
|||
@ -0,0 +1,198 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Verifies affine warped-motion prediction through native SIMD and scalar execution.
|
|||
/// </summary>
|
|||
[Trait("Format", "Avif")] |
|||
public class Av1WarpedInterPredictorTests |
|||
{ |
|||
/// <summary>
|
|||
/// The hardware configurations covering the portable vector operator and scalar fallback.
|
|||
/// </summary>
|
|||
private const HwIntrinsics PredictorConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic; |
|||
|
|||
/// <summary>
|
|||
/// Verifies exact 8-bit vector/scalar parity for luma and subsampled chroma coordinates.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void BytePredictionMatchesScalarAcrossIntrinsicConfigurations() |
|||
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateBytePrediction, PredictorConfigurations); |
|||
|
|||
/// <summary>
|
|||
/// Verifies exact 8-, 10-, and 12-bit vector/scalar parity for luma and subsampled chroma coordinates.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void HighBitDepthPredictionMatchesScalarAcrossIntrinsicConfigurations() |
|||
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHighBitDepthPrediction, PredictorConfigurations); |
|||
|
|||
/// <summary>
|
|||
/// Applies the pinned multi-sample affine model to deterministic byte storage.
|
|||
/// </summary>
|
|||
private static void ValidateBytePrediction() |
|||
{ |
|||
const int activeSize = 96; |
|||
const int padding = 24; |
|||
const int sourceStride = activeSize + (2 * padding); |
|||
const int width = 13; |
|||
const int height = 11; |
|||
const int destinationStride = width + 7; |
|||
byte[] source = new byte[sourceStride * sourceStride]; |
|||
for (int row = 0; row < sourceStride; row++) |
|||
{ |
|||
for (int column = 0; column < sourceStride; column++) |
|||
{ |
|||
source[(row * sourceStride) + column] = (byte)(((row * 29) + (column * 47) + (row * column * 3)) & byte.MaxValue); |
|||
} |
|||
} |
|||
|
|||
Av1GlobalMotionParameters parameters = CreatePinnedParameters(); |
|||
for (int subsampling = 0; subsampling <= 1; subsampling++) |
|||
{ |
|||
byte[] expected = new byte[destinationStride * height]; |
|||
byte[] actual = new byte[destinationStride * height]; |
|||
Array.Fill(expected, (byte)0xD3); |
|||
Array.Fill(actual, (byte)0xD3); |
|||
short[] expectedScratch = new short[Av1InterPredictor.WarpedScratchLength]; |
|||
short[] actualScratch = new short[Av1InterPredictor.WarpedScratchLength]; |
|||
Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12); |
|||
|
|||
Av1InterPredictor.PredictWarpedScalar( |
|||
source, |
|||
sourceStride, |
|||
new Point(padding, padding), |
|||
activeSize, |
|||
activeSize, |
|||
expected, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsampling, |
|||
subsampling, |
|||
parameters, |
|||
expectedScratch); |
|||
|
|||
Av1InterPredictor.PredictWarped( |
|||
source, |
|||
sourceStride, |
|||
new Point(padding, padding), |
|||
activeSize, |
|||
activeSize, |
|||
actual, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsampling, |
|||
subsampling, |
|||
parameters, |
|||
actualScratch); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the pinned multi-sample affine model to every supported high-bit-depth precision.
|
|||
/// </summary>
|
|||
private static void ValidateHighBitDepthPrediction() |
|||
{ |
|||
const int activeSize = 96; |
|||
const int padding = 24; |
|||
const int sourceStride = activeSize + (2 * padding); |
|||
const int width = 13; |
|||
const int height = 11; |
|||
const int destinationStride = width + 7; |
|||
Av1GlobalMotionParameters parameters = CreatePinnedParameters(); |
|||
foreach (int bitDepth in new[] { 8, 10, 12 }) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
ushort[] source = new ushort[sourceStride * sourceStride]; |
|||
for (int row = 0; row < sourceStride; row++) |
|||
{ |
|||
for (int column = 0; column < sourceStride; column++) |
|||
{ |
|||
source[(row * sourceStride) + column] = |
|||
(ushort)(((row * 269) + (column * 443) + (row * column * 31)) & maximum); |
|||
|
|||
} |
|||
} |
|||
|
|||
for (int subsampling = 0; subsampling <= 1; subsampling++) |
|||
{ |
|||
ushort[] expected = new ushort[destinationStride * height]; |
|||
ushort[] actual = new ushort[destinationStride * height]; |
|||
Array.Fill(expected, (ushort)0xDEAD); |
|||
Array.Fill(actual, (ushort)0xDEAD); |
|||
short[] expectedScratch = new short[Av1InterPredictor.WarpedScratchLength]; |
|||
short[] actualScratch = new short[Av1InterPredictor.WarpedScratchLength]; |
|||
Point destinationPosition = subsampling == 0 ? new Point(32, 24) : new Point(16, 12); |
|||
|
|||
Av1InterPredictor.PredictWarpedScalar( |
|||
source, |
|||
sourceStride, |
|||
new Point(padding, padding), |
|||
activeSize, |
|||
activeSize, |
|||
expected, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsampling, |
|||
subsampling, |
|||
bitDepth, |
|||
parameters, |
|||
expectedScratch); |
|||
|
|||
Av1InterPredictor.PredictWarped( |
|||
source, |
|||
sourceStride, |
|||
new Point(padding, padding), |
|||
activeSize, |
|||
activeSize, |
|||
actual, |
|||
destinationStride, |
|||
destinationPosition, |
|||
width, |
|||
height, |
|||
subsampling, |
|||
subsampling, |
|||
bitDepth, |
|||
parameters, |
|||
actualScratch); |
|||
|
|||
Assert.Equal(expected, actual); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates one nontrivial affine model traced from the pinned two-frame local-warp fixture.
|
|||
/// </summary>
|
|||
private static Av1GlobalMotionParameters CreatePinnedParameters() |
|||
{ |
|||
Av1GlobalMotionParameters parameters = Av1GlobalMotionParameters.Identity; |
|||
parameters.Type = Av1GlobalMotionType.Affine; |
|||
parameters[0] = -191565; |
|||
parameters[1] = 599107; |
|||
parameters[2] = 61755; |
|||
parameters[3] = -140; |
|||
parameters[4] = -6909; |
|||
parameters[5] = 62012; |
|||
parameters.UpdateShearParameters(); |
|||
Assert.False(parameters.IsInvalid); |
|||
Assert.Equal(-3776, parameters.Alpha); |
|||
Assert.Equal(-128, parameters.Beta); |
|||
Assert.Equal(-7360, parameters.Gamma); |
|||
Assert.Equal(-3520, parameters.Delta); |
|||
return parameters; |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:4490d62fb6679378e92caca48427359091ad2106be49fc1a3848f78be03beeb1 |
|||
size 10084 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:990bac4ad443005c217b0da4fccfa9adfb3aa147ad06c85f9a655a4433e9e8a7 |
|||
size 3307 |
|||
Loading…
Reference in new issue