// 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;
}