diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs index 6a30dc970..16b42e694 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs @@ -3,6 +3,7 @@ using System.Numerics; using SixLabors.ImageSharp.Formats.Heif.Hevc; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; @@ -12,6 +13,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; [Trait("Format", "Heic")] public class HevcIntraPredictorTests { + /// + /// The hardware configurations required to exercise each SIMD tier and the complete scalar fallback. + /// + private const HwIntrinsics PredictorConfigurations = + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; + /// /// Verifies fixed four-by-four prediction results derived from the HEVC intra-prediction equations. /// @@ -92,10 +99,23 @@ public class HevcIntraPredictorTests } /// - /// Verifies that eligible thirty-two-sample references use strong bilinear smoothing rather than local three-tap filtering. + /// Verifies strong bilinear and normal three-tap reference filtering through every SIMD tier and the scalar fallback. + /// + [Fact] + public void ReferenceFiltersMatchScalarDefinitionsAcrossIntrinsicWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateReferenceFilters, PredictorConfigurations); + + /// + /// Compares every prediction mode and block width with a specification-shaped scalar oracle through every SIMD tier. /// [Fact] - public void StrongSmoothingReplacesEligibleNonlinearReferences() + public void EveryModeMatchesScalarOracleAcrossIntrinsicWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateEveryMode, PredictorConfigurations); + + /// + /// Verifies strong bilinear and normal three-tap reference filtering under the selected hardware configuration. + /// + private static void ValidateReferenceFilters() { const int size = 32; ushort[] top = new ushort[(size * 2) + 1]; @@ -123,43 +143,53 @@ public class HevcIntraPredictorTests Assert.Equal((ushort)(100 + i), filteredTop[i]); Assert.Equal((ushort)(100 + (2 * i)), filteredLeft[i]); } + + HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 5, 10, false); + + Assert.Equal((ushort)((left[1] + (2 * top[0]) + top[1] + 2) >> 2), filteredTop[0]); + Assert.Equal(filteredTop[0], filteredLeft[0]); + for (int i = 1; i < top.Length - 1; i++) + { + Assert.Equal((ushort)((top[i - 1] + (2 * top[i]) + top[i + 1] + 2) >> 2), filteredTop[i]); + Assert.Equal((ushort)((left[i - 1] + (2 * left[i]) + left[i + 1] + 2) >> 2), filteredLeft[i]); + } + + Assert.Equal(top[^1], filteredTop[^1]); + Assert.Equal(left[^1], filteredLeft[^1]); } /// - /// Compares every prediction mode and block width with a specification-shaped scalar oracle. + /// Compares every prediction mode and block width with the scalar oracle under the selected hardware configuration. /// - /// The base-two logarithm of the tested block side. - /// The reconstructed component precision. - [Theory] - [InlineData(2, 8)] - [InlineData(3, 10)] - [InlineData(4, 12)] - [InlineData(5, 12)] - public void EveryModeMatchesScalarOracle(int log2Size, int bitDepth) + private static void ValidateEveryMode() { - int size = 1 << log2Size; - int maximum = (1 << bitDepth) - 1; - int referenceLength = (size * 2) + 1; - ushort[] top = new ushort[referenceLength]; - ushort[] left = new ushort[referenceLength]; - top[0] = left[0] = (ushort)(maximum / 3); - for (int i = 1; i < referenceLength; i++) + ReadOnlySpan<(int Log2Size, int BitDepth)> cases = [(2, 8), (3, 10), (4, 12), (5, 12)]; + foreach ((int log2Size, int bitDepth) in cases) { - top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum); - left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum); - } + int size = 1 << log2Size; + int maximum = (1 << bitDepth) - 1; + int referenceLength = (size * 2) + 1; + ushort[] top = new ushort[referenceLength]; + ushort[] left = new ushort[referenceLength]; + top[0] = left[0] = (ushort)(maximum / 3); + for (int i = 1; i < referenceLength; i++) + { + top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum); + left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum); + } - int stride = size + 3; - ushort[] expected = new ushort[stride * size]; - ushort[] actual = new ushort[stride * size]; - ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)]; - for (int mode = 0; mode <= 34; mode++) - { - expected.AsSpan().Clear(); - actual.AsSpan().Clear(); - PredictScalar(top, left, expected, stride, size, mode, bitDepth, true); - HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch); - Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle."); + int stride = size + 3; + ushort[] expected = new ushort[stride * size]; + ushort[] actual = new ushort[stride * size]; + ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)]; + for (int mode = 0; mode <= 34; mode++) + { + expected.AsSpan().Clear(); + actual.AsSpan().Clear(); + PredictScalar(top, left, expected, stride, size, mode, bitDepth, true); + HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch); + Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle."); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs index 3205c6712..58f818dd7 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs @@ -80,6 +80,70 @@ public class HevcPictureDecoderTests } } + /// + /// Verifies the official Main Still Picture stream containing every luma and chroma intra mode at every + /// conformance block size against its published native planar output. + /// + [Fact] + public void DecodeOfficialIntraPredictionPictureMatchesPublishedReference() + { + byte[] annexB = TestFile.Create(TestImages.Heif.IntraPredictionB).Bytes; + byte[] expectedYuv = TestFile.Create(TestImages.Heif.IntraPredictionBReference).Bytes; + ConvertAnnexBStillPicture(annexB, 8, 1, out byte[] configurationData, out byte[] itemData); + HevcCodecConfiguration configuration = new(configurationData); + HevcImageItemBitstream bitstream = new(itemData, configuration); + HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet; + HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; + using HevcPictureDecoder decoder = new(Configuration.Default, pictureParameterSet); + + decoder.Decode(bitstream); + + Assert.Equal(3, configuration.GeneralProfileIdc); + Assert.Equal(1920, sequenceParameterSet.DisplayWidth); + Assert.Equal(1080, sequenceParameterSet.DisplayHeight); + Assert.True(sequenceParameterSet.StrongIntraSmoothingEnabled); + Assert.False(sequenceParameterSet.IntraSmoothingDisabled); + Assert.False(pictureParameterSet.ConstrainedIntraPredictionEnabled); + + int expectedLength = sequenceParameterSet.DisplayWidth * sequenceParameterSet.DisplayHeight; + int chromaWidth = GetDisplaySize(sequenceParameterSet.DisplayWidth, decoder.Picture.GetSubsamplingX(HevcPlane.Cb)); + int chromaHeight = GetDisplaySize(sequenceParameterSet.DisplayHeight, decoder.Picture.GetSubsamplingY(HevcPlane.Cb)); + expectedLength += 2 * chromaWidth * chromaHeight; + Assert.Equal(expectedLength, expectedYuv.Length); + + int offset = 0; + AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Y, expectedYuv, ref offset); + AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cb, expectedYuv, ref offset); + AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cr, expectedYuv, ref offset); + Assert.Equal(expectedYuv.Length, offset); + } + + /// + /// Verifies the independently coded first picture of the official constrained-intra stream against its + /// decoded-picture hashes while the production PPS path retains the enabled constraint. + /// + [Fact] + public void DecodeOfficialConstrainedIntraPictureMatchesPublishedDigest() + { + byte[] annexB = TestFile.Create(TestImages.Heif.ConstrainedIntraPredictionA).Bytes; + ConvertAnnexBStillPicture(annexB, 8, 1, out byte[] configurationData, out byte[] itemData); + HevcCodecConfiguration configuration = new(configurationData); + HevcImageItemBitstream bitstream = new(itemData, configuration); + HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet; + HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; + using HevcPictureDecoder decoder = new(Configuration.Default, pictureParameterSet); + + decoder.Decode(bitstream); + + Assert.Equal(1, configuration.GeneralProfileIdc); + Assert.Equal(416, sequenceParameterSet.DisplayWidth); + Assert.Equal(240, sequenceParameterSet.DisplayHeight); + Assert.True(pictureParameterSet.ConstrainedIntraPredictionEnabled); + Assert.Equal("69a20189e6bbb9c088e3adc967244ca1", GetPlaneDigest(decoder.Picture, HevcPlane.Y)); + Assert.Equal("26502d354bb123f54c20413f14360ddb", GetPlaneDigest(decoder.Picture, HevcPlane.Cb)); + Assert.Equal("baafaef47a55ae2e876862b30b3bc720", 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 97a5a9329..6843d4553 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1304,6 +1304,9 @@ public static class TestImages public const string General12Bit420 = "Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1.bit"; public const string General12Bit422 = "Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1.bit"; public const string General12Bit444 = "Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2.bit"; + public const string IntraPredictionB = "Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit"; + public const string IntraPredictionBReference = "Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv"; + public const string ConstrainedIntraPredictionA = "Heif/Hevc/Conformance/CIP_A_Panasonic_3.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/CIP_A_Panasonic_3.bit b/tests/Images/Input/Heif/Hevc/Conformance/CIP_A_Panasonic_3.bit new file mode 100644 index 000000000..72a805dc1 --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/CIP_A_Panasonic_3.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19f6a710e8b2c66f393392af24c11e5fbba10a5c9f6f28c8b26949942f384c1c +size 11957 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit b/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit new file mode 100644 index 000000000..fc511331b --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.bit @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:456608ae5c14fea0496d770ae9614cd3d7c6ae98c14b22d64bbd96b37f49d7a0 +size 29168 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv b/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv new file mode 100644 index 000000000..27e440244 --- /dev/null +++ b/tests/Images/Input/Heif/Hevc/Conformance/IPRED_B_Nokia_3.yuv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69626c83c9905fe2e3796b5425ccf2719b05b76b42cfeb398997a17207982a52 +size 3110400 diff --git a/tests/Images/Input/Heif/Hevc/Conformance/README.md b/tests/Images/Input/Heif/Hevc/Conformance/README.md index 0d2cde298..53fb38abd 100644 --- a/tests/Images/Input/Heif/Hevc/Conformance/README.md +++ b/tests/Images/Input/Heif/Hevc/Conformance/README.md @@ -15,3 +15,10 @@ The Main-profile traversal matrix comes from the official `RQT_A_HHI_4` through - `STRUCT_A_Samsung_7`: `02f02cc9a67222004ae18ee6bebd475a` - `STRUCT_B_Samsung_7`: `1f472e014aff120681e8482c8a04ef93` - `TUSIZE_A_Samsung_1`: `5e6c198c852e92cdcca0e137fc2610c4` + +The intra-prediction fixtures come from the same official HEVC v1 attachment: + +- `IPRED_B_Nokia_3` is the one-picture Main Still Picture stream identified by H.265.1 Table 1. Its published description states that it uses all 35 modes at each of the luma 32x32, 16x16, 8x8, and 4x4 sizes and the chroma 16x16, 8x8, and 4x4 sizes, for 245 mode-and-size combinations. The archive SHA-256 is `738E35AF4CED173E8CCB558464DC6A1B8B791FB1BB957C073633AA6037288CD0`; the published bitstream MD5 is `1be12a94d9da6b84af78cd6cfd5e4407`; and the retained published YUV has MD5 `ec3bcad4f9174404e52d132206b6617b`. +- `CIP_A_Panasonic_3` signals constrained intra prediction and contains one I picture followed by one B picture. The bounded still decoder test extracts only the independently coded I picture. The archive SHA-256 is `5C55B3594E5C951C9F5312913961750283EB2DADD15844FE35F9BEB94A9A548C`, and the published bitstream MD5 is `e1e00592fc8a158da9109b4dc05d03be`. + +HM at commit `9c1f298659ab0cee9dc13d23d0304221575410b9` reproduces the published `IPRED_B_Nokia_3` YUV MD5 exactly and reports decoded-picture hashes `200ef9f7d2efde44ab872a78a40ece42`, `bfe9de3bef5ce5596a71bbfdbfb823fc`, and `5b14acdd5c5bf8c6887160817ee1ad11`. The same pinned decoder reproduces `CIP_A_Panasonic_3`'s published complete two-picture YUV MD5 `4cbf601ba98d63f642defab5eaa12c8d`; its independently coded first picture reports plane hashes `69a20189e6bbb9c088e3adc967244ca1`, `26502d354bb123f54c20413f14360ddb`, and `baafaef47a55ae2e876862b30b3bc720`.