Browse Source

Verify HEVC loop filtering against HM

pull/2633/head
James Jackson-South 4 days ago
parent
commit
702739916b
  1. 51
      src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.BandOperator.cs
  2. 68
      src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.EdgeOperator.cs
  3. 72
      src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.Operator.cs
  4. 176
      src/ImageSharp/Formats/Heif/Hevc/HevcSampleAdaptiveOffsetFilter.cs
  5. 174
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs
  6. 4
      tests/ImageSharp.Tests/TestImages.cs
  7. 3
      tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_MAIN10_VIXS_4.bit
  8. 3
      tests/Images/Input/Heif/Hevc/Conformance/DBLK_A_SONY_3.bit
  9. 7
      tests/Images/Input/Heif/Hevc/Conformance/README.md
  10. 3
      tests/Images/Input/Heif/Hevc/Conformance/SAO_A_MediaTek_4.bit
  11. 3
      tests/Images/Input/Heif/Hevc/Conformance/SAO_A_RExt_MediaTek_1.bit

51
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
{
/// <summary>
/// Classifies samples by one of thirty-two most-significant-value bands.
/// </summary>
private readonly struct BandOperator : ISampleClassifier
{
/// <inheritdoc/>
public static bool UsesNeighbors => false;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> neighbor1,
in KernelParameters kernel)
=> (Vector512.ShiftRightArithmetic(current, kernel.BandShift) - Vector512.Create(kernel.BandPosition)) & Vector512.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel)
=> (Vector256.ShiftRightArithmetic(current, kernel.BandShift) - Vector256.Create(kernel.BandPosition)) & Vector256.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel)
=> (Vector128.ShiftRightArithmetic(current, kernel.BandShift) - Vector128.Create(kernel.BandPosition)) & Vector128.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel)
=> ((current >> kernel.BandShift) - kernel.BandPosition) & 31;
}
}

68
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
{
/// <summary>
/// Classifies samples by the sum of their signs relative to two directional neighbors.
/// </summary>
private readonly struct EdgeOperator : ISampleClassifier
{
/// <inheritdoc/>
public static bool UsesNeighbors => true;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> 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<short> one = Vector512.Create((short)1);
Vector512<short> sign0 = (Vector512.GreaterThan(current, neighbor0) & one) - (Vector512.LessThan(current, neighbor0) & one);
Vector512<short> sign1 = (Vector512.GreaterThan(current, neighbor1) & one) - (Vector512.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector512.Create((short)2);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel)
{
Vector256<short> one = Vector256.Create((short)1);
Vector256<short> sign0 = (Vector256.GreaterThan(current, neighbor0) & one) - (Vector256.LessThan(current, neighbor0) & one);
Vector256<short> sign1 = (Vector256.GreaterThan(current, neighbor1) & one) - (Vector256.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector256.Create((short)2);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel)
{
Vector128<short> one = Vector128.Create((short)1);
Vector128<short> sign0 = (Vector128.GreaterThan(current, neighbor0) & one) - (Vector128.LessThan(current, neighbor0) & one);
Vector128<short> sign1 = (Vector128.GreaterThan(current, neighbor1) & one) - (Vector128.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector128.Create((short)2);
}
/// <inheritdoc/>
[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;
}
}

72
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
{
/// <summary>
/// Defines the sample classifier shared by the SIMD row traversal and scalar tail.
/// </summary>
private interface ISampleClassifier
{
/// <summary>
/// Gets a value indicating whether classification reads the two neighboring sample rows.
/// </summary>
public static abstract bool UsesNeighbors { get; }
/// <summary>
/// Classifies thirty-two current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies sixteen current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies eight current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies one current sample against its two classifier inputs.
/// </summary>
/// <param name="current">The current sample.</param>
/// <param name="neighbor0">The first neighboring sample.</param>
/// <param name="neighbor1">The second neighboring sample.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table index.</returns>
public static abstract int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel);
}
}

176
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.
/// </remarks>
internal static class HevcSampleAdaptiveOffsetFilter
internal static partial class HevcSampleAdaptiveOffsetFilter
{
/// <summary>
/// Defines the sample classifier shared by the SIMD row traversal and scalar tail.
/// </summary>
private interface ISampleClassifier
{
/// <summary>
/// Gets a value indicating whether classification reads the two neighboring sample rows.
/// </summary>
public static abstract bool UsesNeighbors { get; }
/// <summary>
/// Classifies thirty-two current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies sixteen current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies eight current samples against their two classifier inputs.
/// </summary>
/// <param name="current">The current sample lanes.</param>
/// <param name="neighbor0">The first neighboring sample lanes.</param>
/// <param name="neighbor1">The second neighboring sample lanes.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table indices.</returns>
public static abstract Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel);
/// <summary>
/// Classifies one current sample against its two classifier inputs.
/// </summary>
/// <param name="current">The current sample.</param>
/// <param name="neighbor0">The first neighboring sample.</param>
/// <param name="neighbor1">The second neighboring sample.</param>
/// <param name="kernel">The scaled offset and band-class state.</param>
/// <returns>The zero-based offset-table index.</returns>
public static abstract int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel);
}
/// <summary>
/// Applies one resolved sample-adaptive-offset mode to a component coding-tree block.
/// </summary>
@ -201,8 +138,8 @@ internal static class HevcSampleAdaptiveOffsetFilter
Span<ushort> 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<BandClassifier>(sourceRow, sourceRow, sourceRow, destinationRow, in kernel);
// the two neighbor loads when this generic traversal is specialized for BandOperator.
ApplyRow<BandOperator>(sourceRow, sourceRow, sourceRow, destinationRow, in kernel);
}
}
@ -242,7 +179,7 @@ internal static class HevcSampleAdaptiveOffsetFilter
for (int row = y; row < y + height; row++)
{
ReadOnlySpan<ushort> sourceRow = source.GetRowSpan(plane, row);
ApplyRow<EdgeClassifier>(
ApplyRow<EdgeOperator>(
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<EdgeClassifier>(
ApplyRow<EdgeOperator>(
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<EdgeClassifier>(
ApplyRow<EdgeOperator>(
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<EdgeClassifier>(
ApplyRow<EdgeOperator>(
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,
};
/// <summary>
/// Classifies samples by one of thirty-two most-significant-value bands.
/// </summary>
private readonly struct BandClassifier : ISampleClassifier
{
/// <inheritdoc/>
public static bool UsesNeighbors => false;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> neighbor1,
in KernelParameters kernel)
=> (Vector512.ShiftRightArithmetic(current, kernel.BandShift) - Vector512.Create(kernel.BandPosition)) & Vector512.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel)
=> (Vector256.ShiftRightArithmetic(current, kernel.BandShift) - Vector256.Create(kernel.BandPosition)) & Vector256.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel)
=> (Vector128.ShiftRightArithmetic(current, kernel.BandShift) - Vector128.Create(kernel.BandPosition)) & Vector128.Create((short)31);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Classify(short current, short neighbor0, short neighbor1, in KernelParameters kernel)
=> ((current >> kernel.BandShift) - kernel.BandPosition) & 31;
}
/// <summary>
/// Classifies samples by the sum of their signs relative to two directional neighbors.
/// </summary>
private readonly struct EdgeClassifier : ISampleClassifier
{
/// <inheritdoc/>
public static bool UsesNeighbors => true;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector512<short> Classify(
Vector512<short> current,
Vector512<short> neighbor0,
Vector512<short> 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<short> one = Vector512.Create((short)1);
Vector512<short> sign0 = (Vector512.GreaterThan(current, neighbor0) & one) - (Vector512.LessThan(current, neighbor0) & one);
Vector512<short> sign1 = (Vector512.GreaterThan(current, neighbor1) & one) - (Vector512.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector512.Create((short)2);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<short> Classify(
Vector256<short> current,
Vector256<short> neighbor0,
Vector256<short> neighbor1,
in KernelParameters kernel)
{
Vector256<short> one = Vector256.Create((short)1);
Vector256<short> sign0 = (Vector256.GreaterThan(current, neighbor0) & one) - (Vector256.LessThan(current, neighbor0) & one);
Vector256<short> sign1 = (Vector256.GreaterThan(current, neighbor1) & one) - (Vector256.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector256.Create((short)2);
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector128<short> Classify(
Vector128<short> current,
Vector128<short> neighbor0,
Vector128<short> neighbor1,
in KernelParameters kernel)
{
Vector128<short> one = Vector128.Create((short)1);
Vector128<short> sign0 = (Vector128.GreaterThan(current, neighbor0) & one) - (Vector128.LessThan(current, neighbor0) & one);
Vector128<short> sign1 = (Vector128.GreaterThan(current, neighbor1) & one) - (Vector128.LessThan(current, neighbor1) & one);
return sign0 + sign1 + Vector128.Create((short)2);
}
/// <inheritdoc/>
[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;
}
/// <summary>
/// Contains one block's scaled offsets and invariant classification values.
/// </summary>

174
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
{
/// <summary>
/// The hardware configurations required to exercise every SAO vector tier and the scalar fallback.
/// </summary>
private const HwIntrinsics LoopFilterConfigurations =
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic;
/// <summary>
/// Identifies residual-tool signaling that an official independently decoded picture must exercise.
/// </summary>
@ -237,6 +244,173 @@ public class HevcPictureDecoderTests
Assert.Equal(chromaRedDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cr));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="path">The complete official Annex B conformance stream.</param>
/// <param name="bitDepth">The signaled component precision.</param>
/// <param name="chromaFormat">The signaled HEVC chroma-format identifier.</param>
/// <param name="sampleAdaptiveOffsetEnabled">Whether the sequence enables sample-adaptive offset filtering.</param>
/// <param name="expectedWidth">The first independently coded picture's displayed width.</param>
/// <param name="expectedHeight">The first independently coded picture's displayed height.</param>
/// <param name="lumaDigest">The pinned-HM luma-plane digest.</param>
/// <param name="chromaBlueDigest">The pinned-HM blue-difference-plane digest.</param>
/// <param name="chromaRedDigest">The pinned-HM red-difference-plane digest.</param>
[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);
/// <summary>
/// Verifies all official loop-filter pictures through every available SIMD tier and the scalar fallback.
/// </summary>
[Fact]
public void DecodeOfficialLoopFilterPicturesMatchPinnedHmDigestsAcrossIntrinsicWidths()
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateOfficialLoopFilterPictures, LoopFilterConfigurations);
/// <summary>
/// Verifies sample-adaptive-offset reconstruction with split allocator groups and balanced final disposal.
/// </summary>
[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);
}
/// <summary>
/// Verifies the official deblocking and sample-adaptive-offset pictures in the active intrinsic configuration.
/// </summary>
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");
}
/// <summary>
/// Verifies one official loop-filter picture in the active intrinsic configuration.
/// </summary>
/// <param name="path">The complete official Annex B conformance stream.</param>
/// <param name="bitDepth">The signaled component precision.</param>
/// <param name="chromaFormat">The signaled HEVC chroma-format identifier.</param>
/// <param name="sampleAdaptiveOffsetEnabled">Whether the sequence enables sample-adaptive offset filtering.</param>
/// <param name="expectedWidth">The first independently coded picture's displayed width.</param>
/// <param name="expectedHeight">The first independently coded picture's displayed height.</param>
/// <param name="lumaDigest">The pinned-HM luma-plane digest.</param>
/// <param name="chromaBlueDigest">The pinned-HM blue-difference-plane digest.</param>
/// <param name="chromaRedDigest">The pinned-HM red-difference-plane digest.</param>
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));
}
/// <summary>
/// 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.

4
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";

3
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

3
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

7
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`.

3
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

3
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
Loading…
Cancel
Save