From 702739916b2a440ad2dc9bae8b680ca7fcc636c4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 30 Aug 2026 05:41:40 +1000 Subject: [PATCH] Verify HEVC loop filtering against HM --- ...SampleAdaptiveOffsetFilter.BandOperator.cs | 51 +++++ ...SampleAdaptiveOffsetFilter.EdgeOperator.cs | 68 +++++++ ...HevcSampleAdaptiveOffsetFilter.Operator.cs | 72 +++++++ .../Hevc/HevcSampleAdaptiveOffsetFilter.cs | 176 +----------------- .../Heif/Hevc/HevcPictureDecoderTests.cs | 174 +++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 4 + .../Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit | 3 + .../Heif/Hevc/Conformance/DBLK_A_SONY_3.bit | 3 + .../Input/Heif/Hevc/Conformance/README.md | 7 + .../Hevc/Conformance/SAO_A_MediaTek_4.bit | 3 + .../Conformance/SAO_A_RExt_MediaTek_1.bit | 3 + 11 files changed, 395 insertions(+), 169 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.BandOperator.cs create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.EdgeOperator.cs create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.Operator.cs create mode 100644 tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit create mode 100644 tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_SONY_3.bit create mode 100644 tests/Images/Input/Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit create mode 100644 tests/Images/Input/Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.BandOperator.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.BandOperator.cs new file mode 100644 index 000000000..99b40cdd2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.BandOperator.cs @@ -0,0 +1,51 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +internal static partial class HevcSampleAdaptiveOffsetFilter +{ + /// + /// Classifies samples by one of thirty-two most-significant-value bands. + /// + private readonly struct BandOperator : ISampleClassifier + { + /// + public static bool UsesNeighbors => false; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Classify( + Vector512 current, + Vector512 neighbor0, + Vector512 neighbor1, + in KernelParameters kernel) + => (Vector512.ShiftRightArithmetic(current, kernel.BandShift) - Vector512.Create(kernel.BandPosition)) & Vector512.Create((short)31); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Classify( + Vector256 current, + Vector256 neighbor0, + Vector256 neighbor1, + in KernelParameters kernel) + => (Vector256.ShiftRightArithmetic(current, kernel.BandShift) - Vector256.Create(kernel.BandPosition)) & Vector256.Create((short)31); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Classify( + Vector128 current, + Vector128 neighbor0, + Vector128 neighbor1, + in KernelParameters kernel) + => (Vector128.ShiftRightArithmetic(current, kernel.BandShift) - Vector128.Create(kernel.BandPosition)) & Vector128.Create((short)31); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) + => ((current >> kernel.BandShift) - kernel.BandPosition) & 31; + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.EdgeOperator.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.EdgeOperator.cs new file mode 100644 index 000000000..0b07b37c8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.EdgeOperator.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +internal static partial class HevcSampleAdaptiveOffsetFilter +{ + /// + /// Classifies samples by the sum of their signs relative to two directional neighbors. + /// + private readonly struct EdgeOperator : ISampleClassifier + { + /// + public static bool UsesNeighbors => true; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Classify( + Vector512 current, + Vector512 neighbor0, + Vector512 neighbor1, + in KernelParameters kernel) + { + // Each comparison pair produces -1, 0, or 1. Adding two maps the normative edge classes onto the + // contiguous zero-through-four offset-table indices used by the selection kernel. + Vector512 one = Vector512.Create((short)1); + Vector512 sign0 = (Vector512.GreaterThan(current, neighbor0) & one) - (Vector512.LessThan(current, neighbor0) & one); + Vector512 sign1 = (Vector512.GreaterThan(current, neighbor1) & one) - (Vector512.LessThan(current, neighbor1) & one); + return sign0 + sign1 + Vector512.Create((short)2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Classify( + Vector256 current, + Vector256 neighbor0, + Vector256 neighbor1, + in KernelParameters kernel) + { + Vector256 one = Vector256.Create((short)1); + Vector256 sign0 = (Vector256.GreaterThan(current, neighbor0) & one) - (Vector256.LessThan(current, neighbor0) & one); + Vector256 sign1 = (Vector256.GreaterThan(current, neighbor1) & one) - (Vector256.LessThan(current, neighbor1) & one); + return sign0 + sign1 + Vector256.Create((short)2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector128 Classify( + Vector128 current, + Vector128 neighbor0, + Vector128 neighbor1, + in KernelParameters kernel) + { + Vector128 one = Vector128.Create((short)1); + Vector128 sign0 = (Vector128.GreaterThan(current, neighbor0) & one) - (Vector128.LessThan(current, neighbor0) & one); + Vector128 sign1 = (Vector128.GreaterThan(current, neighbor1) & one) - (Vector128.LessThan(current, neighbor1) & one); + return sign0 + sign1 + Vector128.Create((short)2); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) + => Math.Sign(current - neighbor0) + Math.Sign(current - neighbor1) + 2; + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.Operator.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.Operator.cs new file mode 100644 index 000000000..af2adab55 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.Operator.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +internal static partial class HevcSampleAdaptiveOffsetFilter +{ + /// + /// Defines the sample classifier shared by the SIMD row traversal and scalar tail. + /// + private interface ISampleClassifier + { + /// + /// Gets a value indicating whether classification reads the two neighboring sample rows. + /// + public static abstract bool UsesNeighbors { get; } + + /// + /// Classifies thirty-two current samples against their two classifier inputs. + /// + /// The current sample lanes. + /// The first neighboring sample lanes. + /// The second neighboring sample lanes. + /// The scaled offset and band-class state. + /// The zero-based offset-table indices. + public static abstract Vector512 Classify( + Vector512 current, + Vector512 neighbor0, + Vector512 neighbor1, + in KernelParameters kernel); + + /// + /// Classifies sixteen current samples against their two classifier inputs. + /// + /// The current sample lanes. + /// The first neighboring sample lanes. + /// The second neighboring sample lanes. + /// The scaled offset and band-class state. + /// The zero-based offset-table indices. + public static abstract Vector256 Classify( + Vector256 current, + Vector256 neighbor0, + Vector256 neighbor1, + in KernelParameters kernel); + + /// + /// Classifies eight current samples against their two classifier inputs. + /// + /// The current sample lanes. + /// The first neighboring sample lanes. + /// The second neighboring sample lanes. + /// The scaled offset and band-class state. + /// The zero-based offset-table indices. + public static abstract Vector128 Classify( + Vector128 current, + Vector128 neighbor0, + Vector128 neighbor1, + in KernelParameters kernel); + + /// + /// Classifies one current sample against its two classifier inputs. + /// + /// The current sample. + /// The first neighboring sample. + /// The second neighboring sample. + /// The scaled offset and band-class state. + /// The zero-based offset-table index. + public static abstract int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel); + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.cs index 7dda714e8..e6bba520f 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.cs @@ -16,71 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Hevc; /// indices select one of the signaled offsets, after which addition and bit-depth clipping remain lane-wise. A scalar /// continuation handles only incomplete vectors at picture edges. /// -internal static class HevcSampleAdaptiveOffsetFilter +internal static partial class HevcSampleAdaptiveOffsetFilter { - /// - /// Defines the sample classifier shared by the SIMD row traversal and scalar tail. - /// - private interface ISampleClassifier - { - /// - /// Gets a value indicating whether classification reads the two neighboring sample rows. - /// - public static abstract bool UsesNeighbors { get; } - - /// - /// Classifies thirty-two current samples against their two classifier inputs. - /// - /// The current sample lanes. - /// The first neighboring sample lanes. - /// The second neighboring sample lanes. - /// The scaled offset and band-class state. - /// The zero-based offset-table indices. - public static abstract Vector512 Classify( - Vector512 current, - Vector512 neighbor0, - Vector512 neighbor1, - in KernelParameters kernel); - - /// - /// Classifies sixteen current samples against their two classifier inputs. - /// - /// The current sample lanes. - /// The first neighboring sample lanes. - /// The second neighboring sample lanes. - /// The scaled offset and band-class state. - /// The zero-based offset-table indices. - public static abstract Vector256 Classify( - Vector256 current, - Vector256 neighbor0, - Vector256 neighbor1, - in KernelParameters kernel); - - /// - /// Classifies eight current samples against their two classifier inputs. - /// - /// The current sample lanes. - /// The first neighboring sample lanes. - /// The second neighboring sample lanes. - /// The scaled offset and band-class state. - /// The zero-based offset-table indices. - public static abstract Vector128 Classify( - Vector128 current, - Vector128 neighbor0, - Vector128 neighbor1, - in KernelParameters kernel); - - /// - /// Classifies one current sample against its two classifier inputs. - /// - /// The current sample. - /// The first neighboring sample. - /// The second neighboring sample. - /// The scaled offset and band-class state. - /// The zero-based offset-table index. - public static abstract int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel); - } - /// /// Applies one resolved sample-adaptive-offset mode to a component coding-tree block. /// @@ -201,8 +138,8 @@ internal static class HevcSampleAdaptiveOffsetFilter Span destinationRow = destination.GetRowSpan(plane, row).Slice(x, width); // Band classification depends only on the current sample. The closed classifier's UsesNeighbors value removes - // the two neighbor loads when this generic traversal is specialized for BandClassifier. - ApplyRow(sourceRow, sourceRow, sourceRow, destinationRow, in kernel); + // the two neighbor loads when this generic traversal is specialized for BandOperator. + ApplyRow(sourceRow, sourceRow, sourceRow, destinationRow, in kernel); } } @@ -242,7 +179,7 @@ internal static class HevcSampleAdaptiveOffsetFilter for (int row = y; row < y + height; row++) { ReadOnlySpan sourceRow = source.GetRowSpan(plane, row); - ApplyRow( + ApplyRow( sourceRow.Slice(start, count), sourceRow.Slice(start - 1, count), sourceRow.Slice(start + 1, count), @@ -280,7 +217,7 @@ internal static class HevcSampleAdaptiveOffsetFilter int end = y + height - (belowAvailable ? 0 : 1); for (int row = start; row < end; row++) { - ApplyRow( + ApplyRow( source.GetRowSpan(plane, row).Slice(x, width), source.GetRowSpan(plane, row - 1).Slice(x, width), source.GetRowSpan(plane, row + 1).Slice(x, width), @@ -347,7 +284,7 @@ internal static class HevcSampleAdaptiveOffsetFilter continue; } - ApplyRow( + ApplyRow( source.GetRowSpan(plane, row).Slice(start, count), source.GetRowSpan(plane, row - 1).Slice(start - 1, count), source.GetRowSpan(plane, row + 1).Slice(start + 1, count), @@ -414,7 +351,7 @@ internal static class HevcSampleAdaptiveOffsetFilter continue; } - ApplyRow( + ApplyRow( source.GetRowSpan(plane, row).Slice(start, count), source.GetRowSpan(plane, row - 1).Slice(start + 1, count), source.GetRowSpan(plane, row + 1).Slice(start - 1, count), @@ -576,105 +513,6 @@ internal static class HevcSampleAdaptiveOffsetFilter _ => 0, }; - /// - /// Classifies samples by one of thirty-two most-significant-value bands. - /// - private readonly struct BandClassifier : ISampleClassifier - { - /// - public static bool UsesNeighbors => false; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Classify( - Vector512 current, - Vector512 neighbor0, - Vector512 neighbor1, - in KernelParameters kernel) - => (Vector512.ShiftRightArithmetic(current, kernel.BandShift) - Vector512.Create(kernel.BandPosition)) & Vector512.Create((short)31); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Classify( - Vector256 current, - Vector256 neighbor0, - Vector256 neighbor1, - in KernelParameters kernel) - => (Vector256.ShiftRightArithmetic(current, kernel.BandShift) - Vector256.Create(kernel.BandPosition)) & Vector256.Create((short)31); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Classify( - Vector128 current, - Vector128 neighbor0, - Vector128 neighbor1, - in KernelParameters kernel) - => (Vector128.ShiftRightArithmetic(current, kernel.BandShift) - Vector128.Create(kernel.BandPosition)) & Vector128.Create((short)31); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) - => ((current >> kernel.BandShift) - kernel.BandPosition) & 31; - } - - /// - /// Classifies samples by the sum of their signs relative to two directional neighbors. - /// - private readonly struct EdgeClassifier : ISampleClassifier - { - /// - public static bool UsesNeighbors => true; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector512 Classify( - Vector512 current, - Vector512 neighbor0, - Vector512 neighbor1, - in KernelParameters kernel) - { - // Each comparison pair produces -1, 0, or 1. Adding two maps the normative edge classes onto the - // contiguous zero-through-four offset-table indices used by the selection kernel. - Vector512 one = Vector512.Create((short)1); - Vector512 sign0 = (Vector512.GreaterThan(current, neighbor0) & one) - (Vector512.LessThan(current, neighbor0) & one); - Vector512 sign1 = (Vector512.GreaterThan(current, neighbor1) & one) - (Vector512.LessThan(current, neighbor1) & one); - return sign0 + sign1 + Vector512.Create((short)2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 Classify( - Vector256 current, - Vector256 neighbor0, - Vector256 neighbor1, - in KernelParameters kernel) - { - Vector256 one = Vector256.Create((short)1); - Vector256 sign0 = (Vector256.GreaterThan(current, neighbor0) & one) - (Vector256.LessThan(current, neighbor0) & one); - Vector256 sign1 = (Vector256.GreaterThan(current, neighbor1) & one) - (Vector256.LessThan(current, neighbor1) & one); - return sign0 + sign1 + Vector256.Create((short)2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector128 Classify( - Vector128 current, - Vector128 neighbor0, - Vector128 neighbor1, - in KernelParameters kernel) - { - Vector128 one = Vector128.Create((short)1); - Vector128 sign0 = (Vector128.GreaterThan(current, neighbor0) & one) - (Vector128.LessThan(current, neighbor0) & one); - Vector128 sign1 = (Vector128.GreaterThan(current, neighbor1) & one) - (Vector128.LessThan(current, neighbor1) & one); - return sign0 + sign1 + Vector128.Create((short)2); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel) - => Math.Sign(current - neighbor0) + Math.Sign(current - neighbor1) + 2; - } - /// /// Contains one block's scaled offsets and invariant classification values. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs index 5da5de2a1..2c1dc2c06 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs @@ -6,6 +6,7 @@ using System.Security.Cryptography; using SixLabors.ImageSharp.Formats.Heif.Hevc; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Memory; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; @@ -15,6 +16,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; [Trait("Format", "Heif")] public class HevcPictureDecoderTests { + /// + /// The hardware configurations required to exercise every SAO vector tier and the scalar fallback. + /// + private const HwIntrinsics LoopFilterConfigurations = + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; + /// /// Identifies residual-tool signaling that an official independently decoded picture must exercise. /// @@ -237,6 +244,173 @@ public class HevcPictureDecoderTests Assert.Equal(chromaRedDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cr)); } + /// + /// Verifies deblocking and sample-adaptive-offset conformance streams against native-plane digests produced by + /// the pinned HM decoder from output that matches each archive's published checksum. + /// + /// The complete official Annex B conformance stream. + /// The signaled component precision. + /// The signaled HEVC chroma-format identifier. + /// Whether the sequence enables sample-adaptive offset filtering. + /// The first independently coded picture's displayed width. + /// The first independently coded picture's displayed height. + /// The pinned-HM luma-plane digest. + /// The pinned-HM blue-difference-plane digest. + /// The pinned-HM red-difference-plane digest. + [Theory] + [InlineData(TestImages.Heif.DeblockingA, 8, 1, false, 832, 480, "3ea2c2ef1f973345111480e7658908b3", "0390b32143b1a832a385f78229e7e574", "8388f3a8af827da46f1fb52941ec00ad")] + [InlineData(TestImages.Heif.DeblockingMain10, 10, 1, true, 176, 144, "184a72aab144cb474df3c1a289e8692d", "d30e750dd70cae163d00f441a75896fd", "d253c41f06228df215f4616febbad34f")] + [InlineData(TestImages.Heif.SampleAdaptiveOffsetA, 8, 1, true, 416, 240, "08723eb3fb41af96c87becc4f6973234", "230778eb7df0ebc009ca92e9697ec4d6", "e8e21ed380d2272dc38384ecd6515e53")] + [InlineData(TestImages.Heif.SampleAdaptiveOffsetRangeExtensions, 12, 3, true, 2560, 1600, "fb342158a61b6cb3174b99d2e1167d7d", "9ff5400aac0380474882acb903f51f89", "bb320be3c7905a5a220e0066a5edb991")] + public void DecodeOfficialLoopFilterPictureMatchesPinnedHmDigest( + string path, + int bitDepth, + byte chromaFormat, + bool sampleAdaptiveOffsetEnabled, + int expectedWidth, + int expectedHeight, + string lumaDigest, + string chromaBlueDigest, + string chromaRedDigest) + => ValidateOfficialLoopFilterPicture( + path, + bitDepth, + chromaFormat, + sampleAdaptiveOffsetEnabled, + expectedWidth, + expectedHeight, + lumaDigest, + chromaBlueDigest, + chromaRedDigest); + + /// + /// Verifies all official loop-filter pictures through every available SIMD tier and the scalar fallback. + /// + [Fact] + public void DecodeOfficialLoopFilterPicturesMatchPinnedHmDigestsAcrossIntrinsicWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateOfficialLoopFilterPictures, LoopFilterConfigurations); + + /// + /// Verifies sample-adaptive-offset reconstruction with split allocator groups and balanced final disposal. + /// + [Fact] + public void DecodeOfficialLoopFilterPictureWithConstrainedAllocatorMatchesPinnedHmDigest() + { + byte[] annexB = TestFile.Create(TestImages.Heif.SampleAdaptiveOffsetA).Bytes; + ConvertAnnexBStillPicture(annexB, 8, 1, out byte[] configurationData, out byte[] itemData); + HevcCodecConfiguration codecConfiguration = new(configurationData); + HevcImageItemBitstream bitstream = new(itemData, codecConfiguration); + TestMemoryAllocator allocator = new() { BufferCapacityInBytes = 2_048 }; + allocator.EnableNonThreadSafeLogging(); + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = allocator; + using (HevcPictureDecoder decoder = new(configuration, bitstream.SliceSegments[0].PictureParameterSet)) + { + decoder.Decode(bitstream); + + Assert.Equal("08723eb3fb41af96c87becc4f6973234", GetPlaneDigest(decoder.Picture, HevcPlane.Y)); + Assert.Equal("230778eb7df0ebc009ca92e9697ec4d6", GetPlaneDigest(decoder.Picture, HevcPlane.Cb)); + Assert.Equal("e8e21ed380d2272dc38384ecd6515e53", GetPlaneDigest(decoder.Picture, HevcPlane.Cr)); + } + + Assert.NotEmpty(allocator.AllocationLog); + AssertBalancedAllocations(allocator); + } + + /// + /// Verifies the official deblocking and sample-adaptive-offset pictures in the active intrinsic configuration. + /// + private static void ValidateOfficialLoopFilterPictures() + { + ValidateOfficialLoopFilterPicture( + TestImages.Heif.DeblockingA, + 8, + 1, + false, + 832, + 480, + "3ea2c2ef1f973345111480e7658908b3", + "0390b32143b1a832a385f78229e7e574", + "8388f3a8af827da46f1fb52941ec00ad"); + + ValidateOfficialLoopFilterPicture( + TestImages.Heif.DeblockingMain10, + 10, + 1, + true, + 176, + 144, + "184a72aab144cb474df3c1a289e8692d", + "d30e750dd70cae163d00f441a75896fd", + "d253c41f06228df215f4616febbad34f"); + + ValidateOfficialLoopFilterPicture( + TestImages.Heif.SampleAdaptiveOffsetA, + 8, + 1, + true, + 416, + 240, + "08723eb3fb41af96c87becc4f6973234", + "230778eb7df0ebc009ca92e9697ec4d6", + "e8e21ed380d2272dc38384ecd6515e53"); + + ValidateOfficialLoopFilterPicture( + TestImages.Heif.SampleAdaptiveOffsetRangeExtensions, + 12, + 3, + true, + 2560, + 1600, + "fb342158a61b6cb3174b99d2e1167d7d", + "9ff5400aac0380474882acb903f51f89", + "bb320be3c7905a5a220e0066a5edb991"); + } + + /// + /// Verifies one official loop-filter picture in the active intrinsic configuration. + /// + /// The complete official Annex B conformance stream. + /// The signaled component precision. + /// The signaled HEVC chroma-format identifier. + /// Whether the sequence enables sample-adaptive offset filtering. + /// The first independently coded picture's displayed width. + /// The first independently coded picture's displayed height. + /// The pinned-HM luma-plane digest. + /// The pinned-HM blue-difference-plane digest. + /// The pinned-HM red-difference-plane digest. + private static void ValidateOfficialLoopFilterPicture( + string path, + int bitDepth, + byte chromaFormat, + bool sampleAdaptiveOffsetEnabled, + int expectedWidth, + int expectedHeight, + string lumaDigest, + string chromaBlueDigest, + string chromaRedDigest) + { + byte[] annexB = TestFile.Create(path).Bytes; + ConvertAnnexBStillPicture(annexB, bitDepth, chromaFormat, out byte[] configurationData, out byte[] itemData); + HevcCodecConfiguration configuration = new(configurationData); + HevcImageItemBitstream bitstream = new(itemData, configuration); + HevcSliceSegmentHeader sliceHeader = bitstream.SliceSegments[0]; + HevcSequenceParameterSet sequenceParameterSet = sliceHeader.PictureParameterSet.SequenceParameterSet; + using HevcPictureDecoder decoder = new(Configuration.Default, sliceHeader.PictureParameterSet); + + decoder.Decode(bitstream); + + Assert.Equal(expectedWidth, decoder.Picture.Width); + Assert.Equal(expectedHeight, decoder.Picture.Height); + Assert.Equal(bitDepth, decoder.Picture.BitDepthLuma); + Assert.Equal(chromaFormat, decoder.Picture.ChromaFormat); + Assert.Equal(sampleAdaptiveOffsetEnabled, sequenceParameterSet.SampleAdaptiveOffsetEnabled); + Assert.False(sliceHeader.DeblockingFilterDisabled); + Assert.Equal(lumaDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Y)); + Assert.Equal(chromaBlueDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cb)); + Assert.Equal(chromaRedDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cr)); + } + /// /// Verifies coding-tree and transform-tree conformance streams against native-plane digests produced by the /// pinned HM decoder from output that matches each archive's published checksum. diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 210159391..ef68cd737 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1312,6 +1312,10 @@ public static class TestImages public const string ChromaQuantizationAdjustment12Bit444 = "Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2_idr7.bit"; public const string LosslessA = "Heif/Hevc/Conformance/LS_A_Orange_2.bit"; public const string QuantizationMatrixA = "Heif/Hevc/Conformance/QMATRIX_A_RExt_Sony_1.bit"; + public const string DeblockingA = "Heif/Hevc/Conformance/DBLK_A_SONY_3.bit"; + public const string DeblockingMain10 = "Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit"; + public const string SampleAdaptiveOffsetA = "Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit"; + public const string SampleAdaptiveOffsetRangeExtensions = "Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit"; public const string RqtA = "Heif/Hevc/Conformance/RQT_A_HHI_4.bit"; public const string RqtB = "Heif/Hevc/Conformance/RQT_B_HHI_4.bit"; public const string RqtC = "Heif/Hevc/Conformance/RQT_C_HHI_4.bit"; diff --git a/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit b/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit new file mode 100644 index 000000000..0619c68a1 --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a9089207bedddb0c8db28b410441e37d4cff5dd8301cb8ba7d2f75036e2d50c +size 100467 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_SONY_3.bit b/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_SONY_3.bit new file mode 100644 index 000000000..2642388fa --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_SONY_3.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d61a13e04a1678816c0f16cf7a47f5bb918de95ecf1b6fab305e2390c3dd7de9 +size 279115 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/README.md b/tests/Images/Input/Heif/Hevc/Conformance/README.md index 4b86582a3..0a64ce08f 100644 --- a/tests/Images/Input/Heif/Hevc/Conformance/README.md +++ b/tests/Images/Input/Heif/Hevc/Conformance/README.md @@ -30,3 +30,10 @@ The residual-reconstruction fixtures come from the official HEVC v1 and Range Ex - `EXTPREC_MAIN_444_16_INTRA_12BIT_RExt_Sony_1.bit` is the second of the official stream's two concatenated one-picture sequences, retained with its own VPS, SPS, and PPS. The official archive has SHA-256 `66C8E24866210B94A63A29C93F8C1490046CC17B26484C5BD81F805D7BFD18D1`, the original bitstream has SHA-256 `5F0164DCCC333296FB2753068CAFE4985B758DDB7E282358DBB8FAF74986817D`, and the retained sequence has SHA-256 `4107D5BB5DF4F95185E7A8A5EEF8F35970A4769C76901FF590F38F7BC36B268B`. This 12-bit GBR sequence is the one that signals extended-precision processing and transform skip. Its reference plane hashes were produced by the pinned HM commit built with its `HIGH_BITDEPTH` option, which is required by HM for this sequence. - `GENERAL_12b_444_RExt_Sony_2_idr7.bit` is the seventh independently coded picture extracted with its active VPS, SPS, and PPS from the already retained official GENERAL stream. It has SHA-256 `981D97F0ADC228F2E3A739D7A68161F84CD60309745397D5CF24D2F81C009DBC` and contains 39 coding-unit chroma-QP adjustment decisions across 8-, 16-, and 32-sample chroma transforms. - `LS_A_Orange_2.bit` has SHA-256 `14251238F005A4576437429A327B34D216B346AAAC2A7A05C132A3430174B11B` and published MD5 `9118d01cf6b3671038d6f99342c0895e`. The official lossless description states that every coding unit uses transform, quantization, and filtering bypass; the retained first picture exercises the bypass-flag path and its decoded-picture hash reports OK. + +The loop-filter fixtures are the unchanged Annex B payloads from the official HEVC v1 and Range Extensions archives. `HevcPictureDecoderTests.DecodeOfficialLoopFilterPictureMatchesPinnedHmDigest` retains each first independently coded picture, verifies its signaled precision and chroma layout, and compares every native plane with output from the pinned HIGH_BITDEPTH HM decoder. The feature-runner companion repeats the same production decoder path through every available SIMD tier and the scalar fallback. + +- `DBLK_A_SONY_3.bit` is the 8-bit 4:2:0 deblocking stream. The archive SHA-256 is `CE18CD42375359B9FF293D12A95793CC6B56EB75D899AD012C68E25DFA54DFBF`, and the bitstream SHA-256 is `D61A13E04A1678816C0F16CF7A47F5BB918DE95ECF1B6FAB305E2390C3DD7DE9`. Pinned HM reproduces the published complete-output MD5 `42486a48ea12d5ab6cd98ed2e2807cfe`; the retained first picture has Y, Cb, and Cr MD5 values `3ea2c2ef1f973345111480e7658908b3`, `0390b32143b1a832a385f78229e7e574`, and `8388f3a8af827da46f1fb52941ec00ad`. +- `DBLK_A_MAIN10_VIXS_4.bit` is the Main 10 negative-QP deblocking stream. The archive SHA-256 is `D86EC87F98FCCBCEC3758954A7CF430E8AF8CF021D347DFBBED2F1D5DBB00F25`, and the bitstream SHA-256 is `3A9089207BEDDDB0C8DB28B410441E37D4CFF5DD8301CB8BA7D2F75036E2D50C`. Pinned HM reproduces the published complete-output MD5 `c4594956bb9e8303f1662f9eb1bcdf50`; the retained 10-bit 4:2:0 first picture has plane MD5 values `184a72aab144cb474df3c1a289e8692d`, `d30e750dd70cae163d00f441a75896fd`, and `d253c41f06228df215f4616febbad34f`. +- `SAO_A_MediaTek_4.bit` is the 8-bit 4:2:0 sample-adaptive-offset stream. The archive SHA-256 is `E0B5C8F4AB4FB592971F06469DB630D9E7EF72B4D37D729604E2FDAA9DF706C9`, and the bitstream SHA-256 is `88F693AC4AEC4DEA03CB4BC0CB9D8C98A4023A015905369BB62688064FB58944`. Pinned HM reproduces the published complete-output MD5 `272e694a2262a2b34f6248f787a4431d`; the retained first picture has plane MD5 values `08723eb3fb41af96c87becc4f6973234`, `230778eb7df0ebc009ca92e9697ec4d6`, and `e8e21ed380d2272dc38384ecd6515e53`. +- `SAO_A_RExt_MediaTek_1.bit` is the 12-bit 4:4:4 Range Extensions stream for PPS bit-shift scaling of SAO offsets. The archive SHA-256 is `D0B5646150B35FB8E4C1D51F3D451AEDDF964581132C9C49B253E34556BEEA4D`, and the bitstream SHA-256 is `6B0D45B5CA4D4919EAEC5A98DAF0EB4CEBCC6D8B0A12B30B748386CC9358DF13`. Pinned HM reproduces the published complete GBR-output MD5 `126cd42a185b327d86640a9269044bc8`; the retained first picture has G, B, and R plane MD5 values `fb342158a61b6cb3174b99d2e1167d7d`, `9ff5400aac0380474882acb903f51f89`, and `bb320be3c7905a5a220e0066a5edb991`. diff --git a/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit b/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit new file mode 100644 index 000000000..d0726f980 --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88f693ac4aec4dea03cb4bc0cb9d8c98a4023a015905369bb62688064fb58944 +size 52606 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit b/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit new file mode 100644 index 000000000..62febb8cb --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b0d45b5ca4d4919eaec5a98daf0eb4cebcc6d8b0a12b30b748386cc9358df13 +size 1466031