// 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.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; /// /// Verifies AV1 compound-reference selection and compound inter-mode entropy against current official libaom main. /// [Trait("Format", "Avif")] public class Av1CompoundReferenceEntropyTests { /// /// Verifies every binary compound-reference default against libaom's forward Q15 tables. /// [Fact] public void CompoundReferenceDefaultsMatchLibaom() { AssertBinaryDefaults( [1198, 2070, 9166, 7499, 22475], Av1DefaultDistributions.CompoundReferenceType); AssertBinaryDefaults( [ [5284, 3865, 3128], [23152, 14173, 15270], [31774, 25120, 26710], ], Av1DefaultDistributions.UnidirectionalCompoundReference); AssertBinaryDefaults( [ [4946, 9468, 1503], [19891, 22441, 15160], [30731, 31059, 27544], ], Av1DefaultDistributions.CompoundReference); AssertBinaryDefaults( [ [2235, 1423], [17182, 15175], [30606, 30489], ], Av1DefaultDistributions.CompoundBackwardReference); } /// /// Verifies all eight compound inter-mode defaults against libaom's forward Q15 tables. /// [Fact] public void InterCompoundModeDefaultsMatchLibaom() { uint[][] expected = [ [7760, 13823, 15808, 17641, 19156, 20666, 26891], [10730, 19452, 21145, 22749, 24039, 25131, 28724], [10664, 20221, 21588, 22906, 24295, 25387, 28436], [13298, 16984, 20471, 24182, 25067, 25736, 26422], [18904, 23325, 25242, 27432, 27898, 28258, 30758], [10725, 17454, 20124, 22820, 24195, 25168, 26046], [17125, 24273, 25814, 27492, 28214, 28704, 30592], [13046, 23214, 24505, 25942, 27435, 28442, 29330], ]; Av1Distribution[] actual = Av1DefaultDistributions.InterCompoundMode; Assert.Equal(expected.Length, actual.Length); for (int context = 0; context < expected.Length; context++) { Assert.Equal(8, actual[context].NumberOfSymbols); for (int threshold = 0; threshold < expected[context].Length; threshold++) { Assert.Equal((uint)Av1Distribution.ProbabilityTop - expected[context][threshold], actual[context][threshold]); } } } /// /// Verifies that each semantic reference reader selects its requested context row and tree decision. /// [Fact] public void CompoundReferenceReadersUseRequestedDistributions() { bool[] values = [false, true, true, false, true, false]; for (int context = 0; context < 5; context++) { AssertBinaryReader( Av1DefaultDistributions.CompoundReferenceType[context], values, (ref Av1SymbolDecoder decoder) => decoder.ReadCompoundReferenceIsBidirectional(context)); } for (int context = 0; context < 3; context++) { for (int decision = 0; decision < 3; decision++) { AssertBinaryReader( Av1DefaultDistributions.UnidirectionalCompoundReference[context][decision], values, (ref Av1SymbolDecoder decoder) => decoder.ReadUnidirectionalCompoundReference(context, decision)); AssertBinaryReader( Av1DefaultDistributions.CompoundReference[context][decision], values, (ref Av1SymbolDecoder decoder) => decoder.ReadCompoundForwardReference(context, decision)); } for (int decision = 0; decision < 2; decision++) { AssertBinaryReader( Av1DefaultDistributions.CompoundBackwardReference[context][decision], values, (ref Av1SymbolDecoder decoder) => decoder.ReadCompoundBackwardReference(context, decision)); } } } /// /// Verifies the packed-mode-context mapping and all eight compound mode symbols. /// [Fact] public void CompoundModeReaderUsesMappedDistribution() { ReadOnlySpan packedContexts = [0, 1, 33, 34, 35, 66, 67, 68]; for (int context = 0; context < packedContexts.Length; context++) { using Av1SymbolWriter writer = new(Configuration.Default, 3, updateCdf: true); Av1Distribution writerDistribution = Av1DefaultDistributions.InterCompoundMode[context]; writer.WriteSymbol(0, writerDistribution); writer.WriteSymbol(7, writerDistribution); writer.WriteSymbol(3, writerDistribution); using IMemoryOwner encoded = writer.Exit(); Av1SymbolDecoder decoder = new(Configuration.Default, encoded.Memory.Span, 0, updateCdf: true); Assert.Equal(Av1PredictionMode.NearestNearestMotionVector, decoder.ReadInterCompoundMode(packedContexts[context])); Assert.Equal(Av1PredictionMode.NewNewMotionVector, decoder.ReadInterCompoundMode(packedContexts[context])); Assert.Equal(Av1PredictionMode.NewNearestMotionVector, decoder.ReadInterCompoundMode(packedContexts[context])); } } /// /// Verifies the compound-reference type context across intra, single, bidirectional, and unidirectional neighbors. /// [Fact] public void CompoundReferenceTypeContextMatchesLibaom() { Av1BlockModeInfo intra = CreateModeInfo(Av1ReferenceFrameType.Intra, Av1ReferenceFrameType.None); Av1BlockModeInfo singleForward = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.None); Av1BlockModeInfo singleBackward = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.None); Av1BlockModeInfo bidirectional = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.Backward); Av1BlockModeInfo forwardUnidirectional = CreateModeInfo(Av1ReferenceFrameType.Last, Av1ReferenceFrameType.Last2); Av1BlockModeInfo backwardUnidirectional = CreateModeInfo(Av1ReferenceFrameType.Backward, Av1ReferenceFrameType.Alternate); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(null, null)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(intra, null)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(singleForward, null)); Assert.Equal(0, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(bidirectional, null)); Assert.Equal(4, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(forwardUnidirectional, null)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(intra, singleForward)); Assert.Equal(1, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(intra, bidirectional)); Assert.Equal(3, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(intra, forwardUnidirectional)); Assert.Equal(3, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(singleForward, singleForward)); Assert.Equal(1, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(singleForward, singleBackward)); Assert.Equal(0, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(bidirectional, bidirectional)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(bidirectional, forwardUnidirectional)); Assert.Equal(4, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(forwardUnidirectional, forwardUnidirectional)); Assert.Equal(3, Av1SymbolContextHelper.GetCompoundReferenceTypeContext(forwardUnidirectional, backwardUnidirectional)); } /// /// Verifies the exact neighboring-vote groups used by every compound reference-tree decision. /// [Fact] public void CompoundReferenceContextsAggregateNormativeGroups() { InlineArray8 referenceCountStorage = default; Span referenceCounts = referenceCountStorage; referenceCounts[(int)Av1ReferenceFrameType.Last] = 5; referenceCounts[(int)Av1ReferenceFrameType.Last2] = 1; referenceCounts[(int)Av1ReferenceFrameType.Last3] = 2; referenceCounts[(int)Av1ReferenceFrameType.Golden] = 2; referenceCounts[(int)Av1ReferenceFrameType.Backward] = 3; referenceCounts[(int)Av1ReferenceFrameType.Alternate2] = 3; referenceCounts[(int)Av1ReferenceFrameType.Alternate] = 6; Assert.Equal(0, Av1SymbolContextHelper.GetUnidirectionalCompoundBackwardContext(referenceCounts)); Assert.Equal(0, Av1SymbolContextHelper.GetUnidirectionalCompoundLast3OrGoldenContext(referenceCounts)); Assert.Equal(1, Av1SymbolContextHelper.GetUnidirectionalCompoundGoldenContext(referenceCounts)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundForwardLast3OrGoldenContext(referenceCounts)); Assert.Equal(2, Av1SymbolContextHelper.GetCompoundForwardLast2Context(referenceCounts)); Assert.Equal(1, Av1SymbolContextHelper.GetCompoundForwardGoldenContext(referenceCounts)); Assert.Equal(1, Av1SymbolContextHelper.GetCompoundBackwardAlternateContext(referenceCounts)); Assert.Equal(1, Av1SymbolContextHelper.GetCompoundBackwardAlternate2Context(referenceCounts)); } /// /// Verifies compound CDF copying and snapshot update-count reset without sharing mutable state. /// [Fact] public void FrameEntropyLifecycleIncludesCompoundDistributions() { const int updateCount = 20; Av1FrameEntropyContext source = new(0); Av1FrameEntropyContext copy = new(0); Av1FrameEntropyContext snapshot = new(0); for (int i = 0; i < updateCount; i++) { source.CompoundReferenceType[4].Update(1); source.UnidirectionalCompoundReference[2][2].Update(1); source.CompoundReference[1][1].Update(1); source.CompoundBackwardReference[0][1].Update(1); source.InterCompoundMode[7].Update(6); } copy.CopyFrom(source); source.SnapshotTo(snapshot); Assert.Equal(source.CompoundReferenceType[4][0], copy.CompoundReferenceType[4][0]); Assert.Equal(source.UnidirectionalCompoundReference[2][2][0], copy.UnidirectionalCompoundReference[2][2][0]); Assert.Equal(source.CompoundReference[1][1][0], copy.CompoundReference[1][1][0]); Assert.Equal(source.CompoundBackwardReference[0][1][0], copy.CompoundBackwardReference[0][1][0]); Assert.Equal(source.InterCompoundMode[7][6], copy.InterCompoundMode[7][6]); source.CompoundReferenceType[4].Update(0); snapshot.CompoundReferenceType[4].Update(0); source.InterCompoundMode[7].Update(0); snapshot.InterCompoundMode[7].Update(0); Assert.NotEqual(source.CompoundReferenceType[4][0], snapshot.CompoundReferenceType[4][0]); Assert.NotEqual(source.InterCompoundMode[7][0], snapshot.InterCompoundMode[7][0]); } /// /// Verifies one binary reader against a separately adapted writer distribution. /// private static void AssertBinaryReader(Av1Distribution distribution, ReadOnlySpan values, SymbolReader reader) { using Av1SymbolWriter writer = new(Configuration.Default, values.Length, updateCdf: true); foreach (bool value in values) { writer.WriteSymbol(value, distribution); } using IMemoryOwner encoded = writer.Exit(); Av1SymbolDecoder decoder = new(Configuration.Default, encoded.Memory.Span, 0, updateCdf: true); foreach (bool value in values) { Assert.Equal(value, reader(ref decoder)); } } /// /// Verifies one array of binary defaults stored in inverse-cumulative form. /// private static void AssertBinaryDefaults(ReadOnlySpan expected, Av1Distribution[] actual) { Assert.Equal(expected.Length, actual.Length); for (int i = 0; i < expected.Length; i++) { Assert.Equal((uint)Av1Distribution.ProbabilityTop - expected[i], actual[i][0]); Assert.Equal(2, actual[i].NumberOfSymbols); } } /// /// Verifies a matrix of binary defaults stored in inverse-cumulative form. /// private static void AssertBinaryDefaults(uint[][] expected, Av1Distribution[][] actual) { Assert.Equal(expected.Length, actual.Length); for (int row = 0; row < expected.Length; row++) { AssertBinaryDefaults(expected[row], actual[row]); } } /// /// Creates decoded block-mode state with the requested primary and secondary references. /// private static Av1BlockModeInfo CreateModeInfo(Av1ReferenceFrameType primary, Av1ReferenceFrameType secondary) { Av1BlockModeInfo modeInfo = new(Av1BlockSize.Block8x8, Point.Empty); modeInfo.ReferenceFrames[0] = primary; modeInfo.ReferenceFrames[1] = secondary; return modeInfo; } /// /// Invokes one semantic binary symbol reader. /// private delegate bool SymbolReader(ref Av1SymbolDecoder decoder); }