// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
///
/// Verifies the frame-level derivation of AV1 skip-mode reference pairs.
///
[Trait("Format", "Avif")]
public class ObuSkipModeParametersTests
{
///
/// The public theory-data representation of .
///
private const int KeyFrameValue = (int)ObuFrameType.KeyFrame;
///
/// The public theory-data representation of .
///
private const int InterFrameValue = (int)ObuFrameType.InterFrame;
///
/// The public theory-data representation of .
///
private const int SingleReferenceValue = (int)ObuReferenceMode.SingleReference;
///
/// The public theory-data representation of .
///
private const int ReferenceModeSelectValue = (int)ObuReferenceMode.ReferenceModeSelect;
///
/// Verifies signed order-hint distances across the modulo-domain boundary.
///
[Fact]
public void GetRelativeDistanceWrapsWithinConfiguredDomain()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
Assert.Equal(-2, orderHintInfo.GetRelativeDistance(15, 1));
Assert.Equal(2, orderHintInfo.GetRelativeDistance(1, 15));
}
///
/// Verifies that disabled order hints have no temporal ordering.
///
[Fact]
public void GetRelativeDistanceReturnsZeroWhenOrderHintsAreDisabled()
{
ObuOrderHintInfo orderHintInfo = new();
Assert.Equal(0, orderHintInfo.GetRelativeDistance(15, 1));
}
///
/// Verifies that skip mode selects the nearest past and future canonical reference roles.
///
[Fact]
public void DeriveSelectsNearestForwardAndBackwardReferences()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 10, 12, 15]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.True(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.Last, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.Backward, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that the derived pair identifies canonical roles rather than their physical reference-map slots.
///
[Fact]
public void DeriveOrdersCanonicalRolesIndependentlyOfMappedSlots()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 10, 12, 15]);
Span referenceFrameIndices = frameHeader.GetReferenceFrameIndices();
Span referenceOrderHints = frameHeader.GetReferenceOrderHints();
// Several canonical roles deliberately share physical slot seven. The first matching role remains LAST, while
// the future BWDREF role maps to slot four; neither physical slot number becomes part of the derived pair.
referenceFrameIndices.Fill(7);
referenceFrameIndices[(int)Av1ReferenceFrameType.Backward - 1] = 4;
referenceOrderHints[7] = 7;
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.True(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.Last, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.Backward, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that a frame with only future references cannot use skip mode.
///
[Fact]
public void DeriveDisallowsSkipModeWithoutForwardReference()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [9, 10, 11, 12, 13, 14, 15]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.False(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that a forward-only frame selects the two closest distinct past reference orders.
///
[Fact]
public void SelectsTwoForwardReferencesWithoutBackwardReference()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 1, 5, 4]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.True(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.Last, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.Last3, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that modulo wraparound participates in nearest-reference selection.
///
[Fact]
public void DeriveSelectsReferencesAcrossOrderHintWraparound()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(1, [12, 15, 11, 10, 2, 5, 7]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.True(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.Last2, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.Backward, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that skip mode remains unavailable without two temporally distinct usable reference orders.
///
[Fact]
public void DeriveDisallowsSkipModeWithoutReferencePair()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 8, 8, 8, 8, 8, 8]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.False(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies that deriving an ineligible frame clears a reference pair retained by an earlier derivation.
///
[Fact]
public void DeriveClearsPreviousReferencePair()
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 10, 12, 15]);
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
frameHeader.ReferenceMode = ObuReferenceMode.SingleReference;
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.False(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Verifies the frame modes for which the AV1 syntax forbids skip-mode signaling.
///
/// Whether the sequence enables order hints.
/// The numeric coded-frame-type value.
/// The numeric frame-level reference-mode value.
[Theory]
[InlineData(false, InterFrameValue, ReferenceModeSelectValue)]
[InlineData(true, KeyFrameValue, ReferenceModeSelectValue)]
[InlineData(true, InterFrameValue, SingleReferenceValue)]
public void DeriveDisallowsSkipModeForIneligibleFrameSyntax(
bool enableOrderHint,
int frameTypeValue,
int referenceModeValue)
{
ObuOrderHintInfo orderHintInfo = CreateOrderHintInfo();
orderHintInfo.EnableOrderHint = enableOrderHint;
ObuFrameHeader frameHeader = CreateInterFrame(8, [7, 3, 6, 2, 10, 12, 15]);
frameHeader.FrameType = (ObuFrameType)frameTypeValue;
frameHeader.ReferenceMode = (ObuReferenceMode)referenceModeValue;
frameHeader.SkipModeParameters.Derive(orderHintInfo, frameHeader);
Assert.False(frameHeader.SkipModeParameters.SkipModeAllowed);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.FirstReferenceFrame);
Assert.Equal(Av1ReferenceFrameType.None, frameHeader.SkipModeParameters.SecondReferenceFrame);
}
///
/// Creates the four-bit modulo order-hint configuration used by the derivation scenarios.
///
/// The enabled order-hint configuration.
private static ObuOrderHintInfo CreateOrderHintInfo()
=> new()
{
EnableOrderHint = true,
OrderHintBits = 4,
};
///
/// Creates an inter frame whose seven canonical roles map directly to slots zero through six.
///
/// The current frame order hint.
/// The order hint selected by each canonical role.
/// The initialized inter-frame header.
private static ObuFrameHeader CreateInterFrame(uint currentOrderHint, ReadOnlySpan referenceOrderHints)
{
ObuFrameHeader frameHeader = new()
{
FrameType = ObuFrameType.InterFrame,
OrderHint = currentOrderHint,
ReferenceMode = ObuReferenceMode.ReferenceModeSelect,
};
Span referenceFrameIndices = frameHeader.GetReferenceFrameIndices();
Span referenceMapOrderHints = frameHeader.GetReferenceOrderHints();
for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++)
{
referenceFrameIndices[referenceIndex] = (uint)referenceIndex;
referenceMapOrderHints[referenceIndex] = referenceOrderHints[referenceIndex];
}
return frameHeader;
}
}