// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Numerics; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; /// /// Verifies AV1 intra prediction against scalar definitions across the supported hardware-intrinsic configurations. /// [Trait("Format", "Heif")] public class Av1PredictorTests { /// /// The offset within directional reference storage that leaves readable samples before both edge origins. /// private const int ReferenceOrigin = 128; /// /// The hardware configurations required to exercise each SIMD tier and the complete scalar fallback. /// private const HwIntrinsics PredictorConfigurations = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; /// /// Cardinal, base, and adjusted angles covering every directional projection zone. /// private static ReadOnlySpan DirectionalAngles => [36, 45, 54, 67, 90, 104, 113, 126, 135, 148, 157, 166, 180, 194, 203, 212]; /// /// The complete set of AV1 filter-intra coefficient modes. /// private static ReadOnlySpan FilterIntraModes => [ Av1FilterIntraMode.DC, Av1FilterIntraMode.Vertical, Av1FilterIntraMode.Horizontal, Av1FilterIntraMode.Directional157, Av1FilterIntraMode.Paeth, ]; /// /// Verifies DC prediction with each register-width tier and the scalar fallback. /// [Fact] public void DcPredictorsMatchReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDcPredictors, PredictorConfigurations); /// /// Verifies horizontal prediction with each register-width tier and the scalar fallback. /// [Fact] public void HorizontalPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateHorizontalPredictor, PredictorConfigurations); /// /// Verifies vertical prediction with each register-width tier and the scalar fallback. /// [Fact] public void VerticalPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateVerticalPredictor, PredictorConfigurations); /// /// Verifies Paeth prediction with each register-width tier and the scalar fallback. /// [Fact] public void PaethPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePaethPredictor, PredictorConfigurations); /// /// Verifies smooth prediction with each register-width tier and the scalar fallback. /// [Fact] public void SmoothPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothPredictor, PredictorConfigurations); /// /// Verifies horizontal smooth prediction with each register-width tier and the scalar fallback. /// [Fact] public void SmoothHorizontalPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothHorizontalPredictor, PredictorConfigurations); /// /// Verifies vertical smooth prediction with each register-width tier and the scalar fallback. /// [Fact] public void SmoothVerticalPredictorMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateSmoothVerticalPredictor, PredictorConfigurations); /// /// Verifies directional prediction with each register-width tier and the scalar fallback. /// [Fact] public void DirectionalPredictorsMatchReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateDirectionalPredictors, PredictorConfigurations); /// /// Verifies filter-intra prediction with each register-width tier and the scalar fallback. /// [Fact] public void FilterIntraPredictorsMatchReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateFilterIntraPredictors, PredictorConfigurations); /// /// Verifies intra-edge upsampling with Vector128 and the scalar fallback. /// [Fact] public void EdgeUpsamplingMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeUpsampling, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); /// /// Verifies intra-edge filtering with Vector128 and the scalar fallback. /// [Fact] public void EdgeFilteringMatchesReference() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEdgeFiltering, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); /// /// Verifies all four DC neighbor-availability combinations at every AV1 transform size. /// private static void ValidateDcPredictors() { for (int sizeIndex = 0; sizeIndex < (int)Av1TransformSize.AllSizes; sizeIndex++) { Av1TransformSize transformSize = (Av1TransformSize)sizeIndex; int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int stride = width + 5; byte[] above = CreateByteSamples(width, 17); byte[] left = CreateByteSamples(height, 43); short[] aboveHigh = CreateHighBitDepthSamples(width, 17); short[] leftHigh = CreateHighBitDepthSamples(height, 43); for (int availability = 0; availability < 4; availability++) { bool hasLeft = (availability & 1) != 0; bool hasAbove = (availability & 2) != 0; byte[] expected = CreateByteDestination(stride, height); byte[] actual = CreateByteDestination(stride, height); short[] expectedHigh = CreateHighBitDepthDestination(stride, height); short[] actualHigh = CreateHighBitDepthDestination(stride, height); Av1DcIntraPredictor.PredictScalar(hasLeft, hasAbove, expected, stride, above, left, width, height); Av1DcIntraPredictor.Predict(hasLeft, hasAbove, actual, stride, above, left, width, height); Av1DcIntraPredictor.PredictScalar(hasLeft, hasAbove, expectedHigh, stride, aboveHigh, leftHigh, width, height, 12); Av1DcIntraPredictor.Predict(hasLeft, hasAbove, actualHigh, stride, aboveHigh, leftHigh, width, height, 12); Assert.Equal(expected, actual); Assert.Equal(expectedHigh, actualHigh); } } } /// /// Verifies horizontal prediction at every AV1 transform size and sample precision. /// private static void ValidateHorizontalPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.Horizontal); /// /// Verifies vertical prediction at every AV1 transform size and sample precision. /// private static void ValidateVerticalPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.Vertical); /// /// Verifies Paeth prediction at every AV1 transform size and sample precision. /// private static void ValidatePaethPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.Paeth); /// /// Verifies smooth prediction at every AV1 transform size and sample precision. /// private static void ValidateSmoothPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.Smooth); /// /// Verifies horizontal smooth prediction at every AV1 transform size and sample precision. /// private static void ValidateSmoothHorizontalPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.SmoothHorizontal); /// /// Verifies vertical smooth prediction at every AV1 transform size and sample precision. /// private static void ValidateSmoothVerticalPredictor() => ValidateNonDirectionalPredictor(Av1PredictionMode.SmoothVertical); /// /// Verifies one closed non-directional operator at every AV1 transform size and sample precision. /// /// The prediction mode to verify. private static void ValidateNonDirectionalPredictor(Av1PredictionMode mode) { Av1IntraPredictorBase predictor = Av1IntraPredictorBase.GetPredictor(mode); for (int sizeIndex = 0; sizeIndex < (int)Av1TransformSize.AllSizes; sizeIndex++) { Av1TransformSize transformSize = (Av1TransformSize)sizeIndex; int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int stride = width + 5; byte[] aboveStorage = CreateByteSamples(width + 1, 19); byte[] left = CreateByteSamples(height, 71); short[] aboveHighStorage = CreateHighBitDepthSamples(width + 1, 19); short[] leftHigh = CreateHighBitDepthSamples(height, 71); byte[] expected = CreateByteDestination(stride, height); byte[] actual = CreateByteDestination(stride, height); short[] expectedHigh = CreateHighBitDepthDestination(stride, height); short[] actualHigh = CreateHighBitDepthDestination(stride, height); predictor.PredictScalar(expected, stride, aboveStorage.AsSpan(1), left, width, height); predictor.Predict(actual, stride, aboveStorage.AsSpan(1), left, width, height); predictor.PredictScalar(expectedHigh, stride, aboveHighStorage.AsSpan(1), leftHigh, width, height); predictor.Predict(actualHigh, stride, aboveHighStorage.AsSpan(1), leftHigh, width, height); Assert.Equal(expected, actual); Assert.Equal(expectedHigh, actualHigh); } ValidateKnownNonDirectionalVector(mode, predictor); } /// /// Verifies one non-directional operator against a byte-exact reference block and its translated high-bit-depth equivalent. /// /// The prediction mode being verified. /// The closed operator-driven predictor. private static void ValidateKnownNonDirectionalVector(Av1PredictionMode mode, Av1IntraPredictorBase predictor) { byte[] aboveStorage; byte[] left; byte[] expected; if (mode == Av1PredictionMode.Paeth) { aboveStorage = [50, 60, 10, 90, 40]; left = [20, 80, 30, 100]; expected = [ 20, 10, 50, 20, 80, 50, 90, 80, 30, 10, 90, 30, 100, 50, 100, 100, ]; } else { aboveStorage = [0, 20, 40, 60, 80]; left = [20, 40, 60, 80]; expected = mode switch { Av1PredictionMode.Horizontal => [ 20, 20, 20, 20, 40, 40, 40, 40, 60, 60, 60, 60, 80, 80, 80, 80, ], Av1PredictionMode.Vertical => [ 20, 40, 60, 80, 20, 40, 60, 80, 20, 40, 60, 80, 20, 40, 60, 80, ], Av1PredictionMode.Smooth => [ 20, 43, 60, 73, 43, 57, 68, 75, 60, 68, 73, 78, 73, 75, 78, 80, ], Av1PredictionMode.SmoothHorizontal => [ 20, 45, 60, 65, 40, 57, 67, 70, 60, 68, 73, 75, 80, 80, 80, 80, ], _ => [ 20, 40, 60, 80, 45, 57, 68, 80, 60, 67, 73, 80, 65, 70, 75, 80, ], }; } byte[] actual = new byte[16]; predictor.Predict(actual, 4, aboveStorage.AsSpan(1), left, 4, 4); Assert.Equal(expected, actual); const int offset = 512; short[] aboveHighStorage = new short[aboveStorage.Length]; short[] leftHigh = new short[left.Length]; short[] expectedHigh = new short[expected.Length]; short[] actualHigh = new short[16]; for (int i = 0; i < aboveStorage.Length; i++) { aboveHighStorage[i] = (short)(aboveStorage[i] + offset); } for (int i = 0; i < left.Length; i++) { leftHigh[i] = (short)(left[i] + offset); } for (int i = 0; i < expected.Length; i++) { expectedHigh[i] = (short)(expected[i] + offset); } predictor.Predict(actualHigh, 4, aboveHighStorage.AsSpan(1), leftHigh, 4, 4); Assert.Equal(expectedHigh, actualHigh); } /// /// Verifies every directional zone, rectangular transpose, and edge-upsampling index rule. /// private static void ValidateDirectionalPredictors() { byte[] aboveStorage = CreateByteSamples(512, 23); byte[] leftStorage = CreateByteSamples(512, 89); short[] aboveHighStorage = CreateHighBitDepthSamples(512, 23); short[] leftHighStorage = CreateHighBitDepthSamples(512, 89); ReadOnlySpan above = aboveStorage.AsSpan(ReferenceOrigin); ReadOnlySpan left = leftStorage.AsSpan(ReferenceOrigin); ReadOnlySpan aboveHigh = aboveHighStorage.AsSpan(ReferenceOrigin); ReadOnlySpan leftHigh = leftHighStorage.AsSpan(ReferenceOrigin); foreach (int angle in DirectionalAngles) { for (int sizeIndex = 0; sizeIndex < (int)Av1TransformSize.AllSizes; sizeIndex++) { ValidateDirectionalCase((Av1TransformSize)sizeIndex, angle, false, false, above, left, aboveHigh, leftHigh); } } // Edge upsampling is permitted only for small blocks. These cases exercise top-only, both-edge, // and left-only indexing without asking an invalid large transform to consume an upsampled edge. ValidateDirectionalCase(Av1TransformSize.Size4x4, 45, true, false, above, left, aboveHigh, leftHigh); ValidateDirectionalCase(Av1TransformSize.Size4x4, 135, true, true, above, left, aboveHigh, leftHigh); ValidateDirectionalCase(Av1TransformSize.Size4x4, 203, false, true, above, left, aboveHigh, leftHigh); ValidateKnownDirectionalVectors(); } /// /// Verifies all three projection zones against byte-exact reference blocks. /// private static void ValidateKnownDirectionalVectors() { ValidateKnownDirectionalVector( 45, [0, 10, 20, 30, 40, 50, 60, 70, 80, 90], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 20, 30, 40, 50, 30, 40, 50, 60, 40, 50, 60, 70, 50, 60, 70, 80, ]); ValidateKnownDirectionalVector( 135, [5, 10, 20, 30, 40, 50, 60, 70, 80, 90], [5, 50, 60, 70, 80, 90, 100, 110, 120, 130], [ 5, 10, 20, 30, 50, 5, 10, 20, 60, 50, 5, 10, 70, 60, 50, 5, ]); ValidateKnownDirectionalVector( 203, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 10, 20, 30, 40, 50, 60, 70, 80, 90], [ 14, 18, 23, 27, 24, 28, 33, 37, 34, 38, 43, 47, 44, 48, 53, 57, ]); } /// /// Verifies one directional projection and its translated high-bit-depth equivalent. /// /// The adjusted directional angle. /// The top-left prefix followed by the top reference. /// The top-left prefix followed by the left reference. /// The byte-exact predicted block. private static void ValidateKnownDirectionalVector( int angle, ReadOnlySpan aboveStorage, ReadOnlySpan leftStorage, ReadOnlySpan expected) { ReadOnlySpan above = aboveStorage[1..]; ReadOnlySpan left = leftStorage[1..]; byte[] actual = new byte[16]; byte[] scratch = new byte[Av1DirectionalIntraPredictor.ScratchLength]; Av1DirectionalIntraPredictor.Predict(actual, 4, Av1TransformSize.Size4x4, above, left, false, false, angle, scratch); Assert.Equal(expected, actual); const int offset = 512; short[] aboveHighStorage = new short[aboveStorage.Length]; short[] leftHighStorage = new short[leftStorage.Length]; short[] expectedHigh = new short[expected.Length]; short[] actualHigh = new short[16]; short[] scratchHigh = new short[Av1DirectionalIntraPredictor.ScratchLength]; for (int i = 0; i < aboveStorage.Length; i++) { aboveHighStorage[i] = (short)(aboveStorage[i] + offset); } for (int i = 0; i < leftStorage.Length; i++) { leftHighStorage[i] = (short)(leftStorage[i] + offset); } for (int i = 0; i < expected.Length; i++) { expectedHigh[i] = (short)(expected[i] + offset); } Av1DirectionalIntraPredictor.Predict( actualHigh, 4, Av1TransformSize.Size4x4, aboveHighStorage.AsSpan(1), leftHighStorage.AsSpan(1), false, false, angle, scratchHigh); Assert.Equal(expectedHigh, actualHigh); } /// /// Verifies one directional prediction configuration for both native sample representations. /// private static void ValidateDirectionalCase( Av1TransformSize transformSize, int angle, bool upsampleAbove, bool upsampleLeft, ReadOnlySpan above, ReadOnlySpan left, ReadOnlySpan aboveHigh, ReadOnlySpan leftHigh) { int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int stride = width + 5; byte[] expected = CreateByteDestination(stride, height); byte[] actual = CreateByteDestination(stride, height); short[] expectedHigh = CreateHighBitDepthDestination(stride, height); short[] actualHigh = CreateHighBitDepthDestination(stride, height); byte[] scratch = new byte[Av1DirectionalIntraPredictor.ScratchLength]; short[] scratchHigh = new short[Av1DirectionalIntraPredictor.ScratchLength]; Av1DirectionalIntraPredictor.PredictScalar(expected, stride, transformSize, above, left, upsampleAbove, upsampleLeft, angle); Av1DirectionalIntraPredictor.Predict(actual, stride, transformSize, above, left, upsampleAbove, upsampleLeft, angle, scratch); Av1DirectionalIntraPredictor.PredictScalar(expectedHigh, stride, transformSize, aboveHigh, leftHigh, upsampleAbove, upsampleLeft, angle); Av1DirectionalIntraPredictor.Predict(actualHigh, stride, transformSize, aboveHigh, leftHigh, upsampleAbove, upsampleLeft, angle, scratchHigh); Assert.Equal(expected, actual); Assert.Equal(expectedHigh, actualHigh); } /// /// Verifies every filter-intra operator at each transform size permitted by the AV1 syntax. /// private static void ValidateFilterIntraPredictors() { foreach (Av1FilterIntraMode mode in FilterIntraModes) { Av1FilterIntraPredictorBase predictor = Av1FilterIntraPredictorBase.GetPredictor(mode); for (int sizeIndex = 0; sizeIndex < (int)Av1TransformSize.AllSizes; sizeIndex++) { Av1TransformSize transformSize = (Av1TransformSize)sizeIndex; int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); if (width > 32 || height > 32) { continue; } int stride = width + 5; byte[] aboveStorage = CreateByteSamples(width + 1, 29); byte[] left = CreateByteSamples(height, 97); short[] aboveHighStorage = CreateHighBitDepthSamples(width + 1, 29); short[] leftHigh = CreateHighBitDepthSamples(height, 97); byte[] expected = CreateByteDestination(stride, height); byte[] actual = CreateByteDestination(stride, height); short[] expectedHigh = CreateHighBitDepthDestination(stride, height); short[] actualHigh = CreateHighBitDepthDestination(stride, height); byte[] expectedScratch = new byte[Av1FilterIntraPredictorBase.ScratchLength]; byte[] actualScratch = new byte[Av1FilterIntraPredictorBase.ScratchLength]; short[] expectedHighScratch = new short[Av1FilterIntraPredictorBase.ScratchLength]; short[] actualHighScratch = new short[Av1FilterIntraPredictorBase.ScratchLength]; predictor.PredictScalar(expected, stride, aboveStorage.AsSpan(1), left, width, height, expectedScratch); predictor.Predict(actual, stride, aboveStorage.AsSpan(1), left, width, height, actualScratch); predictor.PredictScalar(expectedHigh, stride, aboveHighStorage.AsSpan(1), leftHigh, width, height, 12, expectedHighScratch); predictor.Predict(actualHigh, stride, aboveHighStorage.AsSpan(1), leftHigh, width, height, 12, actualHighScratch); Assert.Equal(expected, actual); Assert.Equal(expectedHigh, actualHigh); } } ValidateKnownFilterIntraVectors(); } /// /// Retains byte-exact reference vectors so scalar and SIMD code cannot share the same mistranslation unnoticed. /// private static void ValidateKnownFilterIntraVectors() { byte[][] expectedByMode = [ [42, 65, 89, 123, 72, 77, 91, 110, 105, 100, 104, 112, 142, 128, 123, 124], [44, 79, 116, 153, 69, 94, 126, 158, 94, 109, 136, 163, 119, 124, 146, 168], [47, 67, 87, 107, 83, 93, 103, 113, 122, 127, 132, 137, 161, 163, 166, 168], [38, 55, 81, 111, 64, 62, 73, 92, 97, 83, 81, 86, 134, 113, 103, 100], [49, 81, 114, 148, 82, 105, 132, 159, 117, 132, 153, 174, 152, 159, 177, 190], ]; // These edge values are the input to the five reference vectors above. The leading top value is the // shared top-left sample addressed through above[-1] by the normative recursive filter process. byte[] aboveStorage = [17, 30, 70, 110, 150]; byte[] left = [40, 80, 120, 160]; short[] aboveHighStorage = [529, 542, 582, 622, 662]; short[] leftHigh = [552, 592, 632, 672]; for (int modeIndex = 0; modeIndex < FilterIntraModes.Length; modeIndex++) { byte[] actual = new byte[16]; byte[] scratch = new byte[Av1FilterIntraPredictorBase.ScratchLength]; short[] actualHigh = new short[16]; short[] expectedHigh = new short[16]; short[] scratchHigh = new short[Av1FilterIntraPredictorBase.ScratchLength]; Av1FilterIntraPredictorBase predictor = Av1FilterIntraPredictorBase.GetPredictor(FilterIntraModes[modeIndex]); predictor.Predict(actual, 4, aboveStorage.AsSpan(1), left, 4, 4, scratch); for (int i = 0; i < expectedHigh.Length; i++) { expectedHigh[i] = (short)(expectedByMode[modeIndex][i] + 512); } predictor.Predict(actualHigh, 4, aboveHighStorage.AsSpan(1), leftHigh, 4, 4, 10, scratchHigh); Assert.Equal(expectedByMode[modeIndex], actual); Assert.Equal(expectedHigh, actualHigh); } } /// /// Verifies vector interleaving, endpoint extension, clamping, and scalar tails in edge upsampling. /// private static void ValidateEdgeUpsampling() { ReadOnlySpan counts = [4, 8, 12, 16]; foreach (int count in counts) { byte[] actual = CreateUpsampleByteEdge(count); byte[] expected = (byte[])actual.Clone(); byte[] scratch = new byte[160]; UpsampleEdgeScalar(expected, count, 8); Av1PredictionDecoder.UpsampleIntraEdge(actual.AsSpan(2), count, scratch); Assert.Equal(expected, actual); ReadOnlySpan bitDepths = [10, 12]; foreach (int bitDepth in bitDepths) { short[] actualHigh = CreateUpsampleHighBitDepthEdge(count, bitDepth); short[] expectedHigh = (short[])actualHigh.Clone(); short[] scratchHigh = new short[160]; UpsampleEdgeScalar(expectedHigh, count, bitDepth); Av1PredictionDecoder.UpsampleIntraEdge(actualHigh.AsSpan(2), count, bitDepth, scratchHigh); Assert.Equal(expectedHigh, actualHigh); } } } /// /// Verifies all three edge-filter kernels across vector boundaries and the maximum normative edge length. /// private static void ValidateEdgeFiltering() { ReadOnlySpan counts = [4, 8, 9, 16, 31, 64, 129]; foreach (int count in counts) { for (int strength = 1; strength <= 3; strength++) { byte[] actual = CreateByteSamples(count, 31); byte[] expected = (byte[])actual.Clone(); byte[] source = (byte[])actual.Clone(); byte[] scratch = new byte[160]; FilterEdgeScalar(source, expected, strength); Av1PredictionDecoder.FilterIntraEdge(ref actual[0], count, strength, scratch); Assert.Equal(expected, actual); short[] actualHigh = CreateHighBitDepthSamples(count, 31); short[] expectedHigh = (short[])actualHigh.Clone(); short[] sourceHigh = (short[])actualHigh.Clone(); short[] scratchHigh = new short[160]; FilterEdgeScalar(sourceHigh, expectedHigh, strength); Av1PredictionDecoder.FilterIntraEdge(ref actualHigh[0], count, strength, scratchHigh); Assert.Equal(expectedHigh, actualHigh); } } } /// /// Creates deterministic 8-bit samples with enough variation to expose lane-order mistakes. /// private static byte[] CreateByteSamples(int length, int seed) { byte[] samples = new byte[length]; for (int i = 0; i < samples.Length; i++) { samples[i] = (byte)(((i * 73) + (seed * 29) + ((i * i) * 7)) & 255); } return samples; } /// /// Creates deterministic 12-bit samples with values spanning the full reconstructed range. /// private static short[] CreateHighBitDepthSamples(int length, int seed) { short[] samples = new short[length]; for (int i = 0; i < samples.Length; i++) { samples[i] = (short)(((i * 977) + (seed * 131) + ((i * i) * 37)) & 4095); } return samples; } /// /// Creates a strided byte destination initialized with a padding sentinel. /// private static byte[] CreateByteDestination(int stride, int height) { byte[] destination = new byte[stride * height]; Array.Fill(destination, (byte)0xCD); return destination; } /// /// Creates a strided high-bit-depth destination initialized with a padding sentinel. /// private static short[] CreateHighBitDepthDestination(int stride, int height) { short[] destination = new short[stride * height]; Array.Fill(destination, (short)-1234); return destination; } /// /// Creates an 8-bit edge with two prefix samples and room for all interleaved outputs. /// private static byte[] CreateUpsampleByteEdge(int count) { byte[] edge = new byte[(2 * count) + 4]; Array.Fill(edge, (byte)0xA5); edge[1] = 231; for (int i = 0; i < count; i++) { edge[i + 2] = (byte)(((i * 97) + 41) & 255); } return edge; } /// /// Creates a high-bit-depth edge containing extrema that exercise interpolation clamping. /// private static short[] CreateUpsampleHighBitDepthEdge(int count, int bitDepth) { int maximum = (1 << bitDepth) - 1; short[] edge = new short[(2 * count) + 4]; Array.Fill(edge, (short)-1); edge[1] = (short)maximum; for (int i = 0; i < count; i++) { edge[i + 2] = (short)((i & 1) == 0 ? 0 : maximum); } return edge; } /// /// Applies the normative four-tap upsampling formula to an edge stored at index two. /// private static void UpsampleEdgeScalar(T[] edge, int count, int bitDepth) where T : unmanaged, IBinaryInteger { T[] input = new T[count + 3]; input[0] = edge[1]; input[1] = edge[1]; for (int i = 0; i < count; i++) { input[i + 2] = edge[i + 2]; } input[count + 2] = input[count + 1]; edge[0] = input[0]; int maximum = (1 << bitDepth) - 1; for (int i = 0; i < count; i++) { int value = -int.CreateChecked(input[i]) + (9 * int.CreateChecked(input[i + 1])) + (9 * int.CreateChecked(input[i + 2])) - int.CreateChecked(input[i + 3]); edge[(2 * i) + 1] = T.CreateChecked(Math.Clamp((value + 8) >> 4, 0, maximum)); edge[(2 * i) + 2] = input[i + 2]; } } /// /// Applies the normative AV1 edge-filter definition to an independent source copy. /// private static void FilterEdgeScalar(T[] source, T[] destination, int strength) where T : unmanaged, IBinaryInteger { ReadOnlySpan kernel = strength switch { 1 => [0, 4, 8, 4, 0], 2 => [0, 5, 6, 5, 0], _ => [2, 4, 4, 4, 2], }; for (int i = 1; i < source.Length; i++) { int sum = 0; for (int tap = 0; tap < kernel.Length; tap++) { int sourceIndex = Math.Clamp(i - 2 + tap, 0, source.Length - 1); sum += int.CreateChecked(source[sourceIndex]) * kernel[tap]; } destination[i] = T.CreateChecked((sum + 8) >> 4); } } }