// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Hevc; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; /// /// Verifies HEVC transform-skip, transquant-bypass, rotation, and residual differential reconstruction. /// [Trait("Format", "Heic")] public class HevcResidualReconstructorTests { /// /// Verifies that lossless transquant bypass preserves or completely reverses coefficient order. /// /// Whether the coefficient order is reversed. [Theory] [InlineData(false)] [InlineData(true)] public void CopyBypassedPreservesOrRotatesCoefficientOrder(bool rotate) { int[] coefficients = new int[1024]; int[] actual = new int[coefficients.Length]; int[] expected = new int[coefficients.Length]; for (int i = 0; i < coefficients.Length; i++) { coefficients[i] = (i * 17) - 8000; } for (int i = 0; i < coefficients.Length; i++) { expected[i] = rotate ? coefficients[coefficients.Length - 1 - i] : coefficients[i]; } HevcResidualReconstructor.CopyBypassed(coefficients, actual, rotate); int mismatch = expected.AsSpan().SequenceEqual(actual) ? -1 : FindFirstMismatch(expected, actual); Assert.True(mismatch < 0, mismatch < 0 ? string.Empty : $"Mismatch at {mismatch}: expected {expected[mismatch]}, actual {actual[mismatch]}."); } /// /// Compares SIMD transform-skip reconstruction with a scalar oracle across transform sizes and signed shift directions. /// /// The transform-block width. /// The transform-block height. /// The reconstructed component precision. /// The transform dynamic range excluding its sign bit. /// The base-two logarithm of the equivalent square transform size. /// Whether extended transform-skip precision applies. /// Whether the complete coefficient order is reversed. [Theory] [InlineData(4, 4, 8, 15, 2, false, true)] [InlineData(4, 8, 8, 15, 3, false, false)] [InlineData(8, 4, 10, 15, 2, false, false)] [InlineData(8, 8, 10, 15, 3, false, false)] [InlineData(16, 16, 12, 15, 4, false, false)] [InlineData(16, 16, 12, 15, 4, true, false)] [InlineData(32, 32, 12, 18, 5, false, false)] public void TransformSkipMatchesScalarOracle( int width, int height, int bitDepth, int maxTransformDynamicRange, int equivalentLog2TransformSize, bool extendedPrecisionProcessingEnabled, bool rotate) { int coefficientCount = width * height; int[] coefficients = new int[coefficientCount]; int[] actual = new int[coefficientCount]; int[] expected = new int[coefficientCount]; for (int i = 0; i < coefficientCount; i++) { coefficients[i] = (((i * 7919) + (width * 257)) & 65535) - 32768; } ApplyTransformSkipScalar( coefficients, expected, bitDepth, maxTransformDynamicRange, equivalentLog2TransformSize, extendedPrecisionProcessingEnabled, rotate); HevcResidualReconstructor.ApplyTransformSkip( coefficients, actual, width, height, bitDepth, maxTransformDynamicRange, equivalentLog2TransformSize, extendedPrecisionProcessingEnabled, rotate); Assert.True(expected.AsSpan().SequenceEqual(actual)); } /// /// Compares SIMD residual differential reconstruction with the sequential normative recurrence. /// /// The square residual-block side. /// The numeric differential accumulation direction. [Theory] [InlineData(4, 1)] [InlineData(4, 2)] [InlineData(8, 1)] [InlineData(8, 2)] [InlineData(16, 1)] [InlineData(16, 2)] [InlineData(32, 1)] [InlineData(32, 2)] public void ResidualDpcmMatchesScalarOracle(int size, int modeValue) { HevcResidualDpcmMode mode = (HevcResidualDpcmMode)modeValue; int[] actual = new int[size * size]; for (int i = 0; i < actual.Length; i++) { actual[i] = (((i * 104729) + (size * 4099)) & 8191) - 4096; } int[] expected = (int[])actual.Clone(); ApplyResidualDpcmScalar(expected, size, size, mode); HevcResidualReconstructor.ApplyResidualDpcm(actual, size, size, mode); Assert.True(expected.AsSpan().SequenceEqual(actual)); } /// /// Verifies signed residual clipping without clipping the thirty-two-bit recurrence accumulator. /// /// The numeric differential accumulation direction. [Theory] [InlineData(1)] [InlineData(2)] public void ResidualDpcmClipsStoredSamples(int modeValue) { HevcResidualDpcmMode mode = (HevcResidualDpcmMode)modeValue; int[] actual = new int[32 * 32]; actual.AsSpan().Fill(3000); int[] expected = (int[])actual.Clone(); ApplyResidualDpcmScalar(expected, 32, 32, mode); HevcResidualReconstructor.ApplyResidualDpcm(actual, 32, 32, mode); Assert.True(expected.AsSpan().SequenceEqual(actual)); Assert.Contains(short.MaxValue, actual); } /// /// Verifies the Range Extensions rotation constraint for non-transformed intra blocks. /// [Fact] public void RotationRequiresEnabledFourWideIntraBlock() { Assert.True(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, true, 4)); Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(false, true, 4)); Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, false, 4)); Assert.False(HevcResidualReconstructor.IsNonTransformedResidualRotated(true, true, 8)); } /// /// Verifies implicit residual differential mode selection, including 4:2:2 chroma angle remapping. /// [Fact] public void ImplicitResidualDpcmFollowsPredictionDirection() { Assert.Equal(HevcResidualDpcmMode.Horizontal, HevcResidualReconstructor.GetImplicitResidualDpcmMode(10, false)); Assert.Equal(HevcResidualDpcmMode.Vertical, HevcResidualReconstructor.GetImplicitResidualDpcmMode(26, false)); Assert.Equal(HevcResidualDpcmMode.None, HevcResidualReconstructor.GetImplicitResidualDpcmMode(18, false)); Assert.Equal(HevcResidualDpcmMode.Horizontal, HevcResidualReconstructor.GetImplicitResidualDpcmMode(10, true)); Assert.Equal(HevcResidualDpcmMode.Vertical, HevcResidualReconstructor.GetImplicitResidualDpcmMode(26, true)); } /// /// Compares cross-component residual prediction with the scalar signed-precision oracle across SIMD widths and a tail. /// /// The luma precision minus the chroma precision. [Theory] [InlineData(-2)] [InlineData(0)] [InlineData(2)] public void CrossComponentPredictionMatchesScalarOracle(int bitDepthDifference) { const int sampleCount = 257; const int alpha = -8; int[] luma = new int[sampleCount]; int[] actual = new int[sampleCount]; int[] expected = new int[sampleCount]; for (int index = 0; index < sampleCount; index++) { luma[index] = (((index * 7919) + 1229) & 65535) - 32768; actual[index] = (((index * 4099) + 811) & 65535) - 32768; expected[index] = actual[index]; } for (int index = 0; index < sampleCount; index++) { int adjustedLuma = bitDepthDifference >= 0 ? luma[index] >> bitDepthDifference : luma[index] << -bitDepthDifference; expected[index] = Math.Clamp(expected[index] + ((alpha * adjustedLuma) >> 3), short.MinValue, short.MaxValue); } HevcResidualReconstructor.ApplyCrossComponentPrediction(luma, actual, sampleCount, alpha, bitDepthDifference); Assert.True(expected.AsSpan().SequenceEqual(actual)); } /// /// Applies the normative transform-skip normalization as a scalar test oracle. /// /// The dequantized coefficients. /// The destination residual block. /// The reconstructed component precision. /// The transform dynamic range excluding its sign bit. /// The base-two logarithm of the equivalent square transform size. /// Whether extended transform-skip precision applies. /// Whether the complete coefficient order is reversed. private static void ApplyTransformSkipScalar( ReadOnlySpan coefficients, Span residual, int bitDepth, int maxTransformDynamicRange, int equivalentLog2TransformSize, bool extendedPrecisionProcessingEnabled, bool rotate) { int shift = maxTransformDynamicRange - bitDepth - equivalentLog2TransformSize; if (extendedPrecisionProcessingEnabled) { shift = Math.Max(0, shift); } for (int i = 0; i < coefficients.Length; i++) { int value = coefficients[rotate ? coefficients.Length - 1 - i : i]; residual[i] = shift > 0 ? (value + (1 << (shift - 1))) >> shift : value << -shift; } } /// /// Applies the normative inverse residual differential recurrence as a scalar test oracle. /// /// The residual block in packed raster order. /// The residual-block width. /// The residual-block height. /// The differential accumulation direction. private static void ApplyResidualDpcmScalar(Span residual, int width, int height, HevcResidualDpcmMode mode) { if (mode == HevcResidualDpcmMode.Vertical) { for (int x = 0; x < width; x++) { int accumulator = residual[x]; for (int y = 1; y < height; y++) { int index = (y * width) + x; accumulator += residual[index]; residual[index] = Math.Clamp(accumulator, short.MinValue, short.MaxValue); } } } else if (mode == HevcResidualDpcmMode.Horizontal) { for (int y = 0; y < height; y++) { int rowOffset = y * width; int accumulator = residual[rowOffset]; for (int x = 1; x < width; x++) { int index = rowOffset + x; accumulator += residual[index]; residual[index] = Math.Clamp(accumulator, short.MinValue, short.MaxValue); } } } } /// /// Finds the first unequal element in two equally sized test buffers. /// /// The expected values. /// The actual values. /// The first unequal index. private static int FindFirstMismatch(ReadOnlySpan expected, ReadOnlySpan actual) { for (int i = 0; i < expected.Length; i++) { if (expected[i] != actual[i]) { return i; } } return -1; } }