// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Hevc; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; /// /// Validates complete HEVC still-picture reconstruction against independently decoded samples. /// [Trait("Format", "Heif")] public class HevcPictureDecoderTests { /// /// Verifies all reconstructed samples from a real HEIC grid tile against the HM reference decoder. /// /// The exact HEVC decoder-configuration record associated with the item. /// The exact HEVC item payload to decode. /// The corresponding planar samples produced by HM. [Theory] [InlineData(TestImages.Heif.Image1TileHvcConfiguration, TestImages.Heif.Image1Tile1Payload, TestImages.Heif.Image1Tile1ReferenceYuv)] [InlineData(TestImages.Heif.Image1TileHvcConfiguration, TestImages.Heif.Image1Tile2Payload, TestImages.Heif.Image1Tile2ReferenceYuv)] [InlineData(TestImages.Heif.Image2TileHvcConfiguration, TestImages.Heif.Image2Tile1Payload, TestImages.Heif.Image2Tile1ReferenceYuv)] [InlineData(TestImages.Heif.Image2TileHvcConfiguration, TestImages.Heif.Image2Tile7Payload, TestImages.Heif.Image2Tile7ReferenceYuv)] [InlineData(TestImages.Heif.DwsampleTileHvcConfiguration, TestImages.Heif.DwsampleTilePayload, TestImages.Heif.DwsampleTileReferenceYuv)] public void DecodeRealHeicTileMatchesHmReference(string configurationPath, string itemPath, string referencePath) { byte[] configurationData = TestFile.Create(configurationPath).Bytes; byte[] itemData = TestFile.Create(itemPath).Bytes; byte[] expectedYuv = TestFile.Create(referencePath).Bytes; HevcCodecConfiguration configuration = new(configurationData); HevcImageItemBitstream bitstream = new(itemData, configuration); HevcSequenceParameterSet sequenceParameterSet = bitstream.SliceSegments[0].PictureParameterSet.SequenceParameterSet; using HevcPictureDecoder decoder = new(Configuration.Default, bitstream.SliceSegments[0].PictureParameterSet); decoder.Decode(bitstream); int expectedLength = sequenceParameterSet.DisplayWidth * sequenceParameterSet.DisplayHeight; if (decoder.Picture.ChromaFormat != 0) { 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); if (decoder.Picture.ChromaFormat != 0) { AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cb, expectedYuv, ref offset); AssertPlaneEqual(decoder.Picture, sequenceParameterSet, HevcPlane.Cr, expectedYuv, ref offset); } Assert.Equal(expectedYuv.Length, offset); } /// /// Compares one decoded component plane with its planar reference samples. /// /// The decoded picture containing the component plane. /// The coded and displayed picture geometry. /// The component plane to compare. /// The complete planar YUV reference. /// The current reference offset, advanced past the compared plane. private static void AssertPlaneEqual( HevcPictureBuffer picture, HevcSequenceParameterSet sequenceParameterSet, HevcPlane plane, ReadOnlySpan expected, ref int offset) { int subsamplingX = picture.GetSubsamplingX(plane); int subsamplingY = picture.GetSubsamplingY(plane); int sourceX = sequenceParameterSet.ConformanceWindowLeftOffset >> subsamplingX; int sourceY = sequenceParameterSet.ConformanceWindowTopOffset >> subsamplingY; int width = GetDisplaySize(sequenceParameterSet.DisplayWidth, subsamplingX); int height = GetDisplaySize(sequenceParameterSet.DisplayHeight, subsamplingY); int mismatchCount = 0; int maximumDifference = 0; int firstMismatchX = 0; int firstMismatchY = 0; ushort firstActual = 0; ushort firstExpected = 0; for (int y = 0; y < height; y++) { Span actualRow = picture.GetRowSpan(plane, sourceY + y).Slice(sourceX, width); for (int x = 0; x < width; x++) { ushort expectedSample = expected[offset++]; int difference = Math.Abs(actualRow[x] - expectedSample); if (difference == 0) { continue; } if (mismatchCount == 0) { firstMismatchX = x; firstMismatchY = y; firstActual = actualRow[x]; firstExpected = expectedSample; } mismatchCount++; maximumDifference = Math.Max(maximumDifference, difference); } } Assert.True( mismatchCount == 0, $"{plane} contained {mismatchCount} differing samples. The maximum difference was {maximumDifference}; " + $"the first mismatch at ({firstMismatchX}, {firstMismatchY}) was {firstActual}, expected {firstExpected}."); } /// /// Converts a luma display extent to the selected component extent. /// /// The displayed luma extent. /// The component subsampling shift. /// The displayed component extent. private static int GetDisplaySize(int lumaSize, int subsampling) => (lumaSize + (1 << subsampling) - 1) >> subsampling; }