// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; /// /// Verifies AV1 motion-vector precision, range, spatial-clamp, and temporal-projection semantics. /// [Trait("Format", "Avif")] public class Av1MotionVectorTests { /// /// Verifies that high-precision vectors retain their one-eighth-sample components unchanged. /// [Fact] public void LowerPrecisionRetainsHighPrecisionComponents() { Av1MotionVector vector = new(15, -15); Assert.Equal(vector, vector.LowerPrecision(allowHighPrecision: true, forceInteger: false)); } /// /// Verifies that low precision removes odd one-eighth-sample components toward zero. /// /// The original vertical component. /// The original horizontal component. /// The expected low-precision vertical component. /// The expected low-precision horizontal component. [Theory] [InlineData(15, -15, 14, -14)] [InlineData(14, -14, 14, -14)] [InlineData(1, -1, 0, 0)] public void LowerPrecisionReducesOddComponentsTowardZero(int row, int column, int expectedRow, int expectedColumn) { Av1MotionVector actual = new Av1MotionVector(row, column).LowerPrecision(allowHighPrecision: false, forceInteger: false); Assert.Equal(new Av1MotionVector(expectedRow, expectedColumn), actual); } /// /// Verifies AV1 integer-sample rounding, including half-sample ties toward zero on both signs. /// /// The original component in one-eighth-sample units. /// The expected integer-precision component. [Theory] [InlineData(3, 0)] [InlineData(4, 0)] [InlineData(5, 8)] [InlineData(11, 8)] [InlineData(12, 8)] [InlineData(13, 16)] [InlineData(16, 16)] [InlineData(-3, 0)] [InlineData(-4, 0)] [InlineData(-5, -8)] [InlineData(-11, -8)] [InlineData(-12, -8)] [InlineData(-13, -16)] [InlineData(-16, -16)] public void LowerPrecisionRoundsIntegerHalfTiesTowardZero(int component, int expected) { Av1MotionVector actual = new Av1MotionVector(component, -component).LowerPrecision( allowHighPrecision: true, forceInteger: true); Assert.Equal(new Av1MotionVector(expected, -expected), actual); } /// /// Verifies that AV1 reserves both signed motion-vector endpoints. /// /// The vertical component. /// The horizontal component. /// The expected validity. [Theory] [InlineData(-16383, 16383, true)] [InlineData(-16384, 0, false)] [InlineData(-16385, 0, false)] [InlineData(16384, 0, false)] [InlineData(16385, 0, false)] [InlineData(0, -16384, false)] [InlineData(0, 16384, false)] public void IsValidUsesExclusiveMotionVectorEndpoints(int row, int column, bool expected) => Assert.Equal(expected, new Av1MotionVector(row, column).IsValid); /// /// Verifies the complete-block and sixteen-sample borders used to clamp spatial reference candidates. /// [Fact] public void ClampReferenceMatchesSpatialLimits() { const int blockWidth = 16; const int blockHeight = 8; const int blockToLeftEdge = -256; const int blockToRightEdge = 512; const int blockToTopEdge = -128; const int blockToBottomEdge = 384; Av1MotionVector upper = new Av1MotionVector(1000, 1000).ClampReference( blockWidth, blockHeight, blockToLeftEdge, blockToRightEdge, blockToTopEdge, blockToBottomEdge); Av1MotionVector lower = new Av1MotionVector(-1000, -1000).ClampReference( blockWidth, blockHeight, blockToLeftEdge, blockToRightEdge, blockToTopEdge, blockToBottomEdge); Av1MotionVector inside = new Av1MotionVector(48, -64).ClampReference( blockWidth, blockHeight, blockToLeftEdge, blockToRightEdge, blockToTopEdge, blockToBottomEdge); Assert.Equal(new Av1MotionVector(576, 768), upper); Assert.Equal(new Av1MotionVector(-320, -512), lower); Assert.Equal(new Av1MotionVector(48, -64), inside); } /// /// Verifies AV1 fixed-point temporal projection, distance limiting, symmetric rounding, and endpoint clamping. /// /// The source vertical component. /// The source horizontal component. /// The signed source-to-target frame distance. /// The positive source-to-reference frame distance. /// The expected projected vertical component. /// The expected projected horizontal component. [Theory] [InlineData(64, -96, 2, 4, 32, -48)] [InlineData(64, -96, -2, 4, -32, 48)] [InlineData(2, -2, 1, 3, 1, -1)] [InlineData(31, -31, 40, 40, 31, -31)] [InlineData(4095, -4095, 31, 1, 16383, -16383)] public void ProjectTemporalMatchesReference( int row, int column, int numerator, int denominator, int expectedRow, int expectedColumn) { Av1MotionVector actual = new Av1MotionVector(row, column).ProjectTemporal(numerator, denominator); Assert.Equal(new Av1MotionVector(expectedRow, expectedColumn), actual); } }