mirror of https://github.com/SixLabors/ImageSharp
33 changed files with 2952 additions and 30 deletions
@ -0,0 +1,64 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <summary>
|
|||
/// Derives AV1 compound weights from retained reference display distances.
|
|||
/// </summary>
|
|||
internal static class Av1CompoundDistanceWeights |
|||
{ |
|||
private const int MaximumFrameDistance = 31; |
|||
|
|||
/// <summary>
|
|||
/// Derives the weights applied to the first and second predictors.
|
|||
/// </summary>
|
|||
public static void Derive( |
|||
ObuOrderHintInfo orderHintInfo, |
|||
ObuFrameHeader frameHeader, |
|||
Av1ReferenceFrameType firstReference, |
|||
Av1ReferenceFrameType secondReference, |
|||
out int firstWeight, |
|||
out int secondWeight) |
|||
{ |
|||
ReadOnlySpan<int> quantizedDistanceWeights = [2, 3, 2, 5, 2, 7, 1, MaximumFrameDistance]; |
|||
ReadOnlySpan<int> quantizedDistanceLookup = [9, 7, 11, 5, 12, 4, 13, 3]; |
|||
ReadOnlySpan<uint> referenceFrameIndices = frameHeader.GetReferenceFrameIndices(); |
|||
ReadOnlySpan<uint> referenceOrderHints = frameHeader.GetReferenceOrderHints(); |
|||
int firstCanonicalIndex = (int)firstReference - (int)Av1ReferenceFrameType.Last; |
|||
int secondCanonicalIndex = (int)secondReference - (int)Av1ReferenceFrameType.Last; |
|||
uint firstOrderHint = referenceOrderHints[(int)referenceFrameIndices[firstCanonicalIndex]]; |
|||
uint secondOrderHint = referenceOrderHints[(int)referenceFrameIndices[secondCanonicalIndex]]; |
|||
int secondDistance = Av1Math.Clip3( |
|||
0, |
|||
MaximumFrameDistance, |
|||
Math.Abs(orderHintInfo.GetRelativeDistance(secondOrderHint, frameHeader.OrderHint))); |
|||
|
|||
int firstDistance = Av1Math.Clip3( |
|||
0, |
|||
MaximumFrameDistance, |
|||
Math.Abs(orderHintInfo.GetRelativeDistance(frameHeader.OrderHint, firstOrderHint))); |
|||
|
|||
int order = secondDistance <= firstDistance ? 1 : 0; |
|||
int weightClass = 3; |
|||
if (secondDistance != 0 && firstDistance != 0) |
|||
{ |
|||
for (weightClass = 0; weightClass < 3; weightClass++) |
|||
{ |
|||
int secondScaledDistance = secondDistance * quantizedDistanceWeights[(weightClass * 2) + order]; |
|||
int firstScaledDistance = firstDistance * quantizedDistanceWeights[(weightClass * 2) + (1 - order)]; |
|||
if ((secondDistance > firstDistance && secondScaledDistance < firstScaledDistance) || |
|||
(secondDistance <= firstDistance && secondScaledDistance > firstScaledDistance)) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
firstWeight = quantizedDistanceLookup[(weightClass * 2) + order]; |
|||
secondWeight = quantizedDistanceLookup[(weightClass * 2) + (1 - order)]; |
|||
} |
|||
} |
|||
@ -0,0 +1,501 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Provides distance-weighted and per-sample masked compound blending.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
private const int DistanceWeightBits = 4; |
|||
private const int MaskWeightBits = 6; |
|||
private const int MaximumMaskAlpha = 1 << MaskWeightBits; |
|||
|
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two high-bit-depth predictors with AV1 display-distance weights.
|
|||
/// </summary>
|
|||
public static void DistanceWeighted( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DistanceWeighted(firstVector, secondVector, firstWeight, secondWeight).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (ushort)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref byte destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<byte> maskVector = Vector512.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<byte> maskVector = Vector256.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<byte> maskVector = Vector128.LoadUnsafe(ref maskReference, (nuint)column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two high-bit-depth predictors through a contiguous AV1 alpha mask.
|
|||
/// </summary>
|
|||
public static void Blend( |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<ushort> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ref ushort destinationReference = ref MemoryMarshal.GetReference(destinationRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> firstVector = Vector512.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector512<ushort> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> maskVector = LoadMask512(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> firstVector = Vector256.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector256<ushort> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> maskVector = LoadMask256(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<ushort>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> firstVector = Vector128.LoadUnsafe(ref destinationReference, (nuint)column); |
|||
Vector128<ushort> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> maskVector = LoadMask128(ref maskReference, column); |
|||
Blend(firstVector, secondVector, maskVector).StoreUnsafe(ref destinationReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (ushort)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines two 8-bit predictors with display-distance weights without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void DistanceWeightedScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int firstWeight, |
|||
int secondWeight) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
destinationRow[column] = (byte)(((destinationRow[column] * firstWeight) + (secondRow[column] * secondWeight) + 8) >> DistanceWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Blends two 8-bit predictors through an alpha mask without explicit hardware intrinsics.
|
|||
/// </summary>
|
|||
public static void BlendScalar( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
ReadOnlySpan<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height) |
|||
{ |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ReadOnlySpan<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int alpha = maskRow[column]; |
|||
destinationRow[column] = (byte)(((alpha * destinationRow[column]) + ((MaximumMaskAlpha - alpha) * secondRow[column]) + 32) >> MaskWeightBits); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DistanceWeighted(Vector128<byte> first, Vector128<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DistanceWeighted(Vector256<byte> first, Vector256<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DistanceWeighted(Vector512<byte> first, Vector512<byte> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight), |
|||
DistanceWeighted(first2, second2, firstWeight, secondWeight), |
|||
DistanceWeighted(first3, second3, firstWeight, secondWeight)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> DistanceWeighted(Vector128<ushort> first, Vector128<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> DistanceWeighted(Vector256<ushort> first, Vector256<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> DistanceWeighted(Vector512<ushort> first, Vector512<ushort> second, int firstWeight, int secondWeight) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
DistanceWeighted(first0, second0, firstWeight, secondWeight), |
|||
DistanceWeighted(first1, second1, firstWeight, secondWeight)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> DistanceWeighted(Vector128<int> first, Vector128<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector128.Create(firstWeight)) + (second * Vector128.Create(secondWeight)) + Vector128.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> DistanceWeighted(Vector256<int> first, Vector256<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector256.Create(firstWeight)) + (second * Vector256.Create(secondWeight)) + Vector256.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> DistanceWeighted(Vector512<int> first, Vector512<int> second, int firstWeight, int secondWeight) |
|||
=> ((first * Vector512.Create(firstWeight)) + (second * Vector512.Create(secondWeight)) + Vector512.Create(8)) >> DistanceWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> Blend(Vector128<byte> first, Vector128<byte> second, Vector128<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector128<int> first0, out Vector128<int> first1, out Vector128<int> first2, out Vector128<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector128<int> second0, out Vector128<int> second1, out Vector128<int> second2, out Vector128<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector128<int> mask0, out Vector128<int> mask1, out Vector128<int> mask2, out Vector128<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> Blend(Vector256<byte> first, Vector256<byte> second, Vector256<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector256<int> first0, out Vector256<int> first1, out Vector256<int> first2, out Vector256<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector256<int> second0, out Vector256<int> second1, out Vector256<int> second2, out Vector256<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector256<int> mask0, out Vector256<int> mask1, out Vector256<int> mask2, out Vector256<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> Blend(Vector512<byte> first, Vector512<byte> second, Vector512<byte> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first, out Vector512<int> first0, out Vector512<int> first1, out Vector512<int> first2, out Vector512<int> first3); |
|||
Av1IntraPredictorBase.Widen(second, out Vector512<int> second0, out Vector512<int> second1, out Vector512<int> second2, out Vector512<int> second3); |
|||
Av1IntraPredictorBase.Widen(mask, out Vector512<int> mask0, out Vector512<int> mask1, out Vector512<int> mask2, out Vector512<int> mask3); |
|||
return Av1IntraPredictorBase.Narrow( |
|||
Blend(first0, second0, mask0), |
|||
Blend(first1, second1, mask1), |
|||
Blend(first2, second2, mask2), |
|||
Blend(first3, second3, mask3)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> Blend(Vector128<ushort> first, Vector128<ushort> second, Vector128<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector128<int> first0, out Vector128<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector128<int> second0, out Vector128<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector128<int> mask0, out Vector128<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> Blend(Vector256<ushort> first, Vector256<ushort> second, Vector256<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector256<int> first0, out Vector256<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector256<int> second0, out Vector256<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector256<int> mask0, out Vector256<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> Blend(Vector512<ushort> first, Vector512<ushort> second, Vector512<ushort> mask) |
|||
{ |
|||
Av1IntraPredictorBase.Widen(first.AsInt16(), out Vector512<int> first0, out Vector512<int> first1); |
|||
Av1IntraPredictorBase.Widen(second.AsInt16(), out Vector512<int> second0, out Vector512<int> second1); |
|||
Av1IntraPredictorBase.Widen(mask.AsInt16(), out Vector512<int> mask0, out Vector512<int> mask1); |
|||
return Av1IntraPredictorBase.Narrow(Blend(first0, second0, mask0), Blend(first1, second1, mask1)).AsUInt16(); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> Blend(Vector128<int> first, Vector128<int> second, Vector128<int> mask) |
|||
=> ((mask * first) + ((Vector128.Create(MaximumMaskAlpha) - mask) * second) + Vector128.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> Blend(Vector256<int> first, Vector256<int> second, Vector256<int> mask) |
|||
=> ((mask * first) + ((Vector256.Create(MaximumMaskAlpha) - mask) * second) + Vector256.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> Blend(Vector512<int> first, Vector512<int> second, Vector512<int> mask) |
|||
=> ((mask * first) + ((Vector512.Create(MaximumMaskAlpha) - mask) * second) + Vector512.Create(32)) >> MaskWeightBits; |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> LoadMask128(ref byte source, int offset) |
|||
{ |
|||
Vector64<byte> packed = Unsafe.As<byte, Vector64<byte>>(ref Unsafe.Add(ref source, offset)); |
|||
return Vector128.WidenLower(Vector128.Create(packed, Vector64<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> LoadMask256(ref byte source, int offset) |
|||
{ |
|||
Vector128<byte> packed = Vector128.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector256.WidenLower(Vector256.Create(packed, Vector128<byte>.Zero)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> LoadMask512(ref byte source, int offset) |
|||
{ |
|||
Vector256<byte> packed = Vector256.LoadUnsafe(ref source, (nuint)offset); |
|||
return Vector512.WidenLower(Vector512.Create(packed, Vector256<byte>.Zero)); |
|||
} |
|||
} |
|||
@ -0,0 +1,294 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <content>
|
|||
/// Produces the smooth inter-intra and predictor-difference masks used by compound blending.
|
|||
/// </content>
|
|||
internal static partial class Av1CompoundInterPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Gets libaom's one-dimensional inter-intra alpha curve.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> InterIntraWeights => |
|||
[ |
|||
60, 58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 39, 38, 37, 35, |
|||
34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 22, 21, 20, |
|||
19, 19, 18, 18, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 12, |
|||
11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, |
|||
6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, |
|||
4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, |
|||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, |
|||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Fills a smooth inter-intra mask for one plane.
|
|||
/// </summary>
|
|||
public static void FillInterIntraMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
int width, |
|||
int height, |
|||
Av1InterIntraMode mode, |
|||
bool invert) |
|||
{ |
|||
int sizeScale = 128 / Math.Max(width, height); |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int alpha = mode switch |
|||
{ |
|||
Av1InterIntraMode.Vertical => InterIntraWeights[row * sizeScale], |
|||
Av1InterIntraMode.Horizontal => InterIntraWeights[column * sizeScale], |
|||
Av1InterIntraMode.Smooth => InterIntraWeights[Math.Min(row, column) * sizeScale], |
|||
_ => 32, |
|||
}; |
|||
|
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills an 8-bit difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<byte> first, |
|||
int firstStride, |
|||
ReadOnlySpan<byte> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<byte> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<byte> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref byte firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref byte secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<byte> firstVector = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<byte> secondVector = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<byte> firstVector = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<byte> secondVector = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<byte> firstVector = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<byte> secondVector = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
DifferenceWeighted(firstVector, secondVector, 4, invert).StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int difference = Math.Abs(firstRow[column] - secondRow[column]) >> 4; |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); |
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills a high-bit-depth difference-weighted compound mask.
|
|||
/// </summary>
|
|||
public static void FillDifferenceWeightedMask( |
|||
Span<byte> mask, |
|||
int maskStride, |
|||
ReadOnlySpan<ushort> first, |
|||
int firstStride, |
|||
ReadOnlySpan<ushort> second, |
|||
int secondStride, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1DifferenceWeightedMaskType maskType) |
|||
{ |
|||
bool invert = maskType == Av1DifferenceWeightedMaskType.Type38Inverse; |
|||
int differenceShift = bitDepth - 8 + 4; |
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> maskRow = mask.Slice(row * maskStride, width); |
|||
ReadOnlySpan<ushort> firstRow = first.Slice(row * firstStride, width); |
|||
ReadOnlySpan<ushort> secondRow = second.Slice(row * secondStride, width); |
|||
ref byte maskReference = ref MemoryMarshal.GetReference(maskRow); |
|||
ref ushort firstReference = ref MemoryMarshal.GetReference(firstRow); |
|||
ref ushort secondReference = ref MemoryMarshal.GetReference(secondRow); |
|||
int column = 0; |
|||
|
|||
// Two input vectors narrow to one packed byte mask. This keeps mask construction contiguous and avoids
|
|||
// temporary buffers before the following vector blend consumes the complete plane block.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector512<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector512<byte>.Count) |
|||
{ |
|||
Vector512<ushort> first0 = Vector512.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector512<ushort> first1 = Vector512.LoadUnsafe(ref firstReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
Vector512<ushort> second0 = Vector512.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector512<ushort> second1 = Vector512.LoadUnsafe(ref secondReference, (nuint)(column + Vector512<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector256<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector256<byte>.Count) |
|||
{ |
|||
Vector256<ushort> first0 = Vector256.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector256<ushort> first1 = Vector256.LoadUnsafe(ref firstReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
Vector256<ushort> second0 = Vector256.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector256<ushort> second1 = Vector256.LoadUnsafe(ref secondReference, (nuint)(column + Vector256<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int vectorEnd = width - Vector128<byte>.Count; |
|||
for (; column <= vectorEnd; column += Vector128<byte>.Count) |
|||
{ |
|||
Vector128<ushort> first0 = Vector128.LoadUnsafe(ref firstReference, (nuint)column); |
|||
Vector128<ushort> first1 = Vector128.LoadUnsafe(ref firstReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
Vector128<ushort> second0 = Vector128.LoadUnsafe(ref secondReference, (nuint)column); |
|||
Vector128<ushort> second1 = Vector128.LoadUnsafe(ref secondReference, (nuint)(column + Vector128<ushort>.Count)); |
|||
DifferenceWeighted(first0, first1, second0, second1, differenceShift, invert) |
|||
.StoreUnsafe(ref maskReference, (nuint)column); |
|||
} |
|||
} |
|||
|
|||
for (; column < width; column++) |
|||
{ |
|||
int difference = Math.Abs(firstRow[column] - secondRow[column]) >> differenceShift; |
|||
int alpha = Math.Min(MaximumMaskAlpha, 38 + difference); |
|||
maskRow[column] = (byte)(invert ? MaximumMaskAlpha - alpha : alpha); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DifferenceWeighted(Vector128<byte> first, Vector128<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector128<byte> difference = Vector128.Max(first, second) - Vector128.Min(first, second); |
|||
Vector128<ushort> lower = DifferenceWeightedAlpha(Vector128.WidenLower(difference), shift, invert); |
|||
Vector128<ushort> upper = DifferenceWeightedAlpha(Vector128.WidenUpper(difference), shift, invert); |
|||
return Vector128.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DifferenceWeighted(Vector256<byte> first, Vector256<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector256<byte> difference = Vector256.Max(first, second) - Vector256.Min(first, second); |
|||
Vector256<ushort> lower = DifferenceWeightedAlpha(Vector256.WidenLower(difference), shift, invert); |
|||
Vector256<ushort> upper = DifferenceWeightedAlpha(Vector256.WidenUpper(difference), shift, invert); |
|||
return Vector256.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DifferenceWeighted(Vector512<byte> first, Vector512<byte> second, int shift, bool invert) |
|||
{ |
|||
Vector512<byte> difference = Vector512.Max(first, second) - Vector512.Min(first, second); |
|||
Vector512<ushort> lower = DifferenceWeightedAlpha(Vector512.WidenLower(difference), shift, invert); |
|||
Vector512<ushort> upper = DifferenceWeightedAlpha(Vector512.WidenUpper(difference), shift, invert); |
|||
return Vector512.Narrow(lower, upper); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<byte> DifferenceWeighted( |
|||
Vector128<ushort> first0, |
|||
Vector128<ushort> first1, |
|||
Vector128<ushort> second0, |
|||
Vector128<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector128.Narrow( |
|||
DifferenceWeightedAlpha(Vector128.Max(first0, second0) - Vector128.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector128.Max(first1, second1) - Vector128.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<byte> DifferenceWeighted( |
|||
Vector256<ushort> first0, |
|||
Vector256<ushort> first1, |
|||
Vector256<ushort> second0, |
|||
Vector256<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector256.Narrow( |
|||
DifferenceWeightedAlpha(Vector256.Max(first0, second0) - Vector256.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector256.Max(first1, second1) - Vector256.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<byte> DifferenceWeighted( |
|||
Vector512<ushort> first0, |
|||
Vector512<ushort> first1, |
|||
Vector512<ushort> second0, |
|||
Vector512<ushort> second1, |
|||
int shift, |
|||
bool invert) |
|||
=> Vector512.Narrow( |
|||
DifferenceWeightedAlpha(Vector512.Max(first0, second0) - Vector512.Min(first0, second0), shift, invert), |
|||
DifferenceWeightedAlpha(Vector512.Max(first1, second1) - Vector512.Min(first1, second1), shift, invert)); |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<ushort> DifferenceWeightedAlpha(Vector128<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector128<ushort> maximum = Vector128.Create((ushort)MaximumMaskAlpha); |
|||
Vector128<ushort> alpha = Vector128.Min(maximum, (difference >> shift) + Vector128.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<ushort> DifferenceWeightedAlpha(Vector256<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector256<ushort> maximum = Vector256.Create((ushort)MaximumMaskAlpha); |
|||
Vector256<ushort> alpha = Vector256.Min(maximum, (difference >> shift) + Vector256.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<ushort> DifferenceWeightedAlpha(Vector512<ushort> difference, int shift, bool invert) |
|||
{ |
|||
Vector512<ushort> maximum = Vector512.Create((ushort)MaximumMaskAlpha); |
|||
Vector512<ushort> alpha = Vector512.Min(maximum, (difference >> shift) + Vector512.Create((ushort)38)); |
|||
return invert ? maximum - alpha : alpha; |
|||
} |
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; |
|||
|
|||
/// <summary>
|
|||
/// Produces AV1 wedge masks in caller-owned plane-sized storage.
|
|||
/// </summary>
|
|||
internal static class Av1WedgeMask |
|||
{ |
|||
private const int MaximumAlpha = 64; |
|||
private const int MasterSize = 64; |
|||
|
|||
/// <summary>
|
|||
/// Gets the odd-row oblique prototype from pinned libaom.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> MasterObliqueOdd => |
|||
[ |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 6, 18, |
|||
37, 53, 60, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the even-row oblique prototype from pinned libaom.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> MasterObliqueEven => |
|||
[ |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 11, 27, |
|||
46, 58, 62, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical prototype from pinned libaom.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> MasterVertical => |
|||
[ |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 7, 21, |
|||
43, 57, 62, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the codebook used when block height exceeds block width.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> HeightGreaterCodebook => |
|||
[ |
|||
2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, |
|||
0, 4, 2, 0, 4, 4, 0, 4, 6, 1, 4, 4, |
|||
2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, |
|||
3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the codebook used when block width exceeds block height.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> HeightLessCodebook => |
|||
[ |
|||
2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, |
|||
1, 2, 4, 1, 4, 4, 1, 6, 4, 0, 4, 4, |
|||
2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, |
|||
3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the codebook used by square blocks.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<byte> EqualCodebook => |
|||
[ |
|||
2, 4, 4, 3, 4, 4, 4, 4, 4, 5, 4, 4, |
|||
0, 4, 2, 0, 4, 6, 1, 2, 4, 1, 6, 4, |
|||
2, 4, 2, 2, 4, 6, 5, 4, 2, 5, 4, 6, |
|||
3, 2, 4, 3, 6, 4, 4, 2, 4, 4, 6, 4, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Fills one luma or subsampled chroma mask for a selected wedge.
|
|||
/// </summary>
|
|||
/// <param name="destination">The caller-owned plane mask.</param>
|
|||
/// <param name="destinationStride">The distance between destination rows.</param>
|
|||
/// <param name="blockSize">The luma block size selecting the wedge codebook.</param>
|
|||
/// <param name="wedgeIndex">The wedge index in the inclusive range zero through fifteen.</param>
|
|||
/// <param name="wedgeSign">The signaled compound wedge orientation.</param>
|
|||
/// <param name="subX">The horizontal plane subsampling shift.</param>
|
|||
/// <param name="subY">The vertical plane subsampling shift.</param>
|
|||
/// <param name="invert">Whether to complement the resulting mask.</param>
|
|||
public static void Fill( |
|||
Span<byte> destination, |
|||
int destinationStride, |
|||
Av1BlockSize blockSize, |
|||
int wedgeIndex, |
|||
bool wedgeSign, |
|||
int subX, |
|||
int subY, |
|||
bool invert) |
|||
{ |
|||
int lumaWidth = blockSize.GetWidth(); |
|||
int lumaHeight = blockSize.GetHeight(); |
|||
int width = Math.Max(4, lumaWidth >> subX); |
|||
int height = Math.Max(4, lumaHeight >> subY); |
|||
ReadOnlySpan<byte> codebook = lumaHeight > lumaWidth |
|||
? HeightGreaterCodebook |
|||
: lumaHeight < lumaWidth ? HeightLessCodebook : EqualCodebook; |
|||
|
|||
int codebookOffset = wedgeIndex * 3; |
|||
int direction = codebook[codebookOffset]; |
|||
int horizontalOffset = (codebook[codebookOffset + 1] * lumaWidth) >> 3; |
|||
int verticalOffset = (codebook[codebookOffset + 2] * lumaHeight) >> 3; |
|||
bool negative = wedgeSign ^ GetSignFlip(blockSize, wedgeIndex); |
|||
int masterRow = (MasterSize / 2) - verticalOffset; |
|||
int masterColumn = (MasterSize / 2) - horizontalOffset; |
|||
|
|||
// Chroma masks are the rounded average of the corresponding two or four luma-mask samples. Producing the
|
|||
// plane mask once keeps the vector blend contiguous and avoids gathering mask bytes in every SIMD lane.
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
|||
int lumaRow = row << subY; |
|||
for (int column = 0; column < width; column++) |
|||
{ |
|||
int lumaColumn = column << subX; |
|||
int mask = GetMasterValue(direction, negative, masterRow + lumaRow, masterColumn + lumaColumn); |
|||
if (subX != 0) |
|||
{ |
|||
mask += GetMasterValue(direction, negative, masterRow + lumaRow, masterColumn + lumaColumn + 1); |
|||
} |
|||
|
|||
if (subY != 0) |
|||
{ |
|||
int lowerMask = GetMasterValue(direction, negative, masterRow + lumaRow + 1, masterColumn + lumaColumn); |
|||
if (subX != 0) |
|||
{ |
|||
lowerMask += GetMasterValue(direction, negative, masterRow + lumaRow + 1, masterColumn + lumaColumn + 1); |
|||
} |
|||
|
|||
mask += lowerMask; |
|||
} |
|||
|
|||
int sampleCountShift = subX + subY; |
|||
if (sampleCountShift != 0) |
|||
{ |
|||
mask = (mask + (1 << (sampleCountShift - 1))) >> sampleCountShift; |
|||
} |
|||
|
|||
destinationRow[column] = (byte)(invert ? MaximumAlpha - mask : mask); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads one value from the generated 64 by 64 master mask.
|
|||
/// </summary>
|
|||
private static int GetMasterValue(int direction, bool negative, int row, int column) |
|||
{ |
|||
int value = direction switch |
|||
{ |
|||
0 => MasterVertical[row], |
|||
1 => MasterVertical[column], |
|||
2 => GetOblique63(column, row), |
|||
3 => GetOblique63(row, column), |
|||
4 => MaximumAlpha - GetOblique63(row, MasterSize - 1 - column), |
|||
_ => MaximumAlpha - GetOblique63(column, MasterSize - 1 - row), |
|||
}; |
|||
|
|||
return negative ? MaximumAlpha - value : value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads one value from the shifted oblique-63 master prototype.
|
|||
/// </summary>
|
|||
private static int GetOblique63(int row, int column) |
|||
{ |
|||
bool oddRow = (row & 1) != 0; |
|||
int shift = (oddRow ? 15 : 16) - (row >> 1); |
|||
int sourceColumn = Av1Math.Clip3(0, MasterSize - 1, column - shift); |
|||
return oddRow ? MasterObliqueOdd[sourceColumn] : MasterObliqueEven[sourceColumn]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets libaom's canonical sign flip for a block and wedge index.
|
|||
/// </summary>
|
|||
private static bool GetSignFlip(Av1BlockSize blockSize, int wedgeIndex) |
|||
{ |
|||
ReadOnlySpan<byte> signFlips = blockSize switch |
|||
{ |
|||
Av1BlockSize.Block8x8 or Av1BlockSize.Block16x16 or Av1BlockSize.Block32x32 => |
|||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], |
|||
Av1BlockSize.Block8x32 => |
|||
[1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1], |
|||
Av1BlockSize.Block32x8 => |
|||
[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1], |
|||
_ => |
|||
[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1], |
|||
}; |
|||
|
|||
return signFlips[wedgeIndex] != 0; |
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Verifies the adaptive syntax distributions used by selectable compound and inter-intra prediction.
|
|||
/// </summary>
|
|||
[Trait("Format", "Avif")] |
|||
public class Av1SelectableCompoundEntropyTests |
|||
{ |
|||
/// <summary>
|
|||
/// Verifies representative and complete multi-symbol defaults against pinned libaom's forward Q15 tables.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DefaultsMatchPinnedLibaom() |
|||
{ |
|||
AssertForwardThresholds(Av1DefaultDistributions.InterIntraMode[1], [1875, 11082, 27332]); |
|||
AssertForwardThresholds(Av1DefaultDistributions.WedgeInterIntra[(int)Av1BlockSize.Block8x8], [20036]); |
|||
AssertForwardThresholds(Av1DefaultDistributions.CompoundType[(int)Av1BlockSize.Block8x8], [23431]); |
|||
AssertForwardThresholds( |
|||
Av1DefaultDistributions.WedgeIndex[(int)Av1BlockSize.Block8x8], |
|||
[2438, 4440, 6599, 8663, 11005, 12874, 15751, 18094, 20359, 22362, 24127, 25702, 27752, 29450, 31171]); |
|||
|
|||
ReadOnlySpan<uint> compoundIndex = [18244, 12865, 7053, 13259, 9334, 4644]; |
|||
ReadOnlySpan<uint> compoundGroupIndex = [26607, 22891, 18840, 24594, 19934, 22674]; |
|||
|
|||
for (int context = 0; context < compoundIndex.Length; context++) |
|||
{ |
|||
AssertForwardThresholds(Av1DefaultDistributions.CompoundIndex[context], [compoundIndex[context]]); |
|||
AssertForwardThresholds(Av1DefaultDistributions.CompoundGroupIndex[context], [compoundGroupIndex[context]]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that every new reader selects and adapts its intended context without consuming adjacent syntax.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void ReadersRoundTripInNormativeOrder() |
|||
{ |
|||
const int groupContext = 4; |
|||
const int compoundIndexContext = 2; |
|||
Av1BlockSize blockSize = Av1BlockSize.Block8x8; |
|||
using Av1SymbolWriter writer = new(Configuration.Default, 32, updateCdf: true); |
|||
writer.WriteSymbol((int)Av1InterIntraMode.Horizontal, Av1DefaultDistributions.InterIntraMode[blockSize.GetSizeGroup()]); |
|||
writer.WriteSymbol(true, Av1DefaultDistributions.WedgeInterIntra[(int)blockSize]); |
|||
writer.WriteSymbol(13, Av1DefaultDistributions.WedgeIndex[(int)blockSize]); |
|||
writer.WriteSymbol(true, Av1DefaultDistributions.CompoundGroupIndex[groupContext]); |
|||
writer.WriteSymbol(false, Av1DefaultDistributions.CompoundIndex[compoundIndexContext]); |
|||
writer.WriteSymbol(1, Av1DefaultDistributions.CompoundType[(int)blockSize]); |
|||
|
|||
using IMemoryOwner<byte> encoded = writer.Exit(); |
|||
Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0, updateCdf: true); |
|||
|
|||
Assert.Equal(Av1InterIntraMode.Horizontal, decoder.ReadInterIntraMode(blockSize)); |
|||
Assert.True(decoder.ReadUseInterIntraWedge(blockSize)); |
|||
Assert.Equal(13, decoder.ReadWedgeIndex(blockSize)); |
|||
Assert.True(decoder.ReadCompoundGroupIndex(groupContext)); |
|||
Assert.False(decoder.ReadCompoundIndex(compoundIndexContext)); |
|||
Assert.Equal(Av1CompoundType.DifferenceWeighted, decoder.ReadMaskedCompoundType(blockSize)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies copying, resetting, and snapshot publication for every selectable-compound distribution family.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void FrameEntropyLifecycleIncludesSelectableCompoundFamilies() |
|||
{ |
|||
Av1FrameEntropyContext source = new(0); |
|||
Av1FrameEntropyContext copy = new(0); |
|||
Av1FrameEntropyContext snapshot = new(0); |
|||
Av1FrameEntropyContext defaults = new(0); |
|||
Av1Distribution[] sourceDistributions = |
|||
[ |
|||
source.InterIntraMode[1], |
|||
source.WedgeInterIntra[(int)Av1BlockSize.Block8x8], |
|||
source.CompoundType[(int)Av1BlockSize.Block8x8], |
|||
source.WedgeIndex[(int)Av1BlockSize.Block8x8], |
|||
source.CompoundIndex[2], |
|||
source.CompoundGroupIndex[4], |
|||
]; |
|||
|
|||
Av1Distribution[] copyDistributions = |
|||
[ |
|||
copy.InterIntraMode[1], |
|||
copy.WedgeInterIntra[(int)Av1BlockSize.Block8x8], |
|||
copy.CompoundType[(int)Av1BlockSize.Block8x8], |
|||
copy.WedgeIndex[(int)Av1BlockSize.Block8x8], |
|||
copy.CompoundIndex[2], |
|||
copy.CompoundGroupIndex[4], |
|||
]; |
|||
|
|||
Av1Distribution[] snapshotDistributions = |
|||
[ |
|||
snapshot.InterIntraMode[1], |
|||
snapshot.WedgeInterIntra[(int)Av1BlockSize.Block8x8], |
|||
snapshot.CompoundType[(int)Av1BlockSize.Block8x8], |
|||
snapshot.WedgeIndex[(int)Av1BlockSize.Block8x8], |
|||
snapshot.CompoundIndex[2], |
|||
snapshot.CompoundGroupIndex[4], |
|||
]; |
|||
|
|||
Av1Distribution[] defaultDistributions = |
|||
[ |
|||
defaults.InterIntraMode[1], |
|||
defaults.WedgeInterIntra[(int)Av1BlockSize.Block8x8], |
|||
defaults.CompoundType[(int)Av1BlockSize.Block8x8], |
|||
defaults.WedgeIndex[(int)Av1BlockSize.Block8x8], |
|||
defaults.CompoundIndex[2], |
|||
defaults.CompoundGroupIndex[4], |
|||
]; |
|||
|
|||
foreach (Av1Distribution distribution in sourceDistributions) |
|||
{ |
|||
distribution.Update(distribution.NumberOfSymbols - 1); |
|||
} |
|||
|
|||
copy.CopyFrom(source); |
|||
source.SnapshotTo(snapshot); |
|||
|
|||
for (int index = 0; index < sourceDistributions.Length; index++) |
|||
{ |
|||
Assert.NotSame(sourceDistributions[index], copyDistributions[index]); |
|||
Assert.NotSame(sourceDistributions[index], snapshotDistributions[index]); |
|||
Assert.Equal(sourceDistributions[index][0], copyDistributions[index][0]); |
|||
Assert.Equal(sourceDistributions[index][0], snapshotDistributions[index][0]); |
|||
} |
|||
|
|||
source.ResetToDefaults(0); |
|||
|
|||
for (int index = 0; index < sourceDistributions.Length; index++) |
|||
{ |
|||
Assert.Equal(defaultDistributions[index][0], sourceDistributions[index][0]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares one inverse-cumulative distribution with pinned forward thresholds.
|
|||
/// </summary>
|
|||
private static void AssertForwardThresholds(Av1Distribution distribution, ReadOnlySpan<uint> forwardThresholds) |
|||
{ |
|||
Assert.Equal(forwardThresholds.Length + 1, distribution.NumberOfSymbols); |
|||
for (int index = 0; index < forwardThresholds.Length; index++) |
|||
{ |
|||
Assert.Equal((uint)Av1Distribution.ProbabilityTop - forwardThresholds[index], distribution[index]); |
|||
} |
|||
} |
|||
} |
|||
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:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c |
|||
size 4544 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:fc6459cd334762d74d9d2654640221e80c463cc01b82b29a5866c9e725abe273 |
|||
size 6423 |
|||
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:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c |
|||
size 4544 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:da710d11c60f03eea209e4360e2fc807b89c49ad50671f0dfb1bcf4ad5ef76dd |
|||
size 6437 |
|||
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:f0de4ccdfb6d95a400e69b69fa4c57f0beeeee75825722c31613385f0b3fd9fc |
|||
size 4551 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:71df22e63626b5bc9001ff1e88076b90f11bb47d18089750853e66f0cbbb084b |
|||
size 6392 |
|||
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:d2cb388c9092ef17c4f0382c0150dd30d6f9d0ee247ff45ab5d7d4d312ceb23c |
|||
size 4544 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:98640640a445055fea3d9e2f4a78feef54e97f8171b472cf57d99156e1c553b2 |
|||
size 6439 |
|||
Loading…
Reference in new issue