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