Browse Source

Complete HEVC Range Extensions conformance

pull/2633/head
James Jackson-South 1 week ago
parent
commit
a577f69050
  1. 4
      HEIF_IMPLEMENTATION_PLAN.md
  2. 6
      src/ImageSharp/Formats/Heif/Hevc/HevcPictureDecoder.Prediction.cs
  3. 6
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs
  4. 401
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs
  5. 10
      tests/ImageSharp.Tests/TestImages.cs
  6. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_420_RExt_Sony_1.bit
  7. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_420_RExt_Sony_1_frame0.yuv
  8. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_422_RExt_Sony_1.bit
  9. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_422_RExt_Sony_1_frame0.yuv
  10. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_444_RExt_Sony_2.bit
  11. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_444_RExt_Sony_2_frame0.yuv
  12. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_400_RExt_Sony_1.bit
  13. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_400_RExt_Sony_1_frame0.yuv
  14. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1.bit
  15. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1_frame0.yuv
  16. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1.bit
  17. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1_frame0.yuv
  18. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2.bit
  19. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2_frame0.yuv
  20. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_400_RExt_Sony_1.bit
  21. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_400_RExt_Sony_1_frame0.yuv
  22. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_420_RExt_Sony_1.bit
  23. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_420_RExt_Sony_1_frame0.yuv
  24. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_444_RExt_Sony_2.bit
  25. 3
      tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_444_RExt_Sony_2_frame0.yuv
  26. 9
      tests/Images/Input/Heif/Hevc/Conformance/README.md

4
HEIF_IMPLEMENTATION_PLAN.md

@ -44,13 +44,15 @@ Checkboxes may be marked complete only when the implementation and the verificat
- [x] Keep optimized color arithmetic, fixed-point parameter storage, SIMD-width traversal, sample loading/storage, chroma filtering, and RGB packing primitives in the shared HEIF color namespace. AV1 and HEVC namespaces adapt their native planes, crop windows, and signaled chroma positions without owning parallel color arithmetic.
- [x] Implement sequential pooled HEVC conversion in both directions for monochrome, 4:2:0, 4:2:2, and 4:4:4; independent luma/chroma precision; 8/10/12-bit full/limited ranges; and all six progressive 4:2:0 chroma sample locations. Eight-bit paths use JPEG's optimized RGB plane contracts, while high-bit-depth paths retain 16-bit packed pixels.
- [x] Implement allocation-free SIMD-first HEVC intra prediction for all 35 modes, 4/8/16/32 blocks, and 8/10/12-bit samples, including three-tap and strong-bilinear reference filtering, negative-angle reference extension, luma boundary filters, and SIMD horizontal transposition. Verify the normal and forced-scalar paths against one scalar oracle.
- [x] Match HM's channel-specific smoothing rules: combined 4:4:4 chroma uses the normal three-tap filter when selected, while strong bilinear smoothing remains restricted to luma syntax. Exact official RExt reconstruction passes at 8, 10, and 12 bits.
- [x] Implement allocation-free reconstructed-plane reference collection and normative substitution for complete, empty, and partially available borders, including asymmetric 4:2:2 availability units and the HEVC mode/size/component filter-selection rules.
- [x] Implement allocation-free SIMD-first HEVC inverse quantization for flat and custom scaling lists, 4/8/16/32 transforms, transform-skip scaling rules, extended precision, signed normalization shifts, and transform-range clipping. Store compact and transform-sized scaling matrices in one contiguous parameter-set allocation so the hot path can widen consecutive weights without coordinate division.
- [x] Derive each reference-unit availability flag from coding-tree, slice, tile, constrained-intra, and picture-boundary state, then connect prediction to transform-unit traversal and the bounded item decoder lifecycle.
- [x] Register the bounded `hvc1` still-image decoder, reconstruct the selected PPS/SPS picture, crop the conformance window directly in the pooled row converter, and pass the native planes through the shared SIMD-first H.273 pipeline.
- [x] Compare native HEVC planes byte-for-byte with HM 18 output for four item payloads extracted from independently produced HEIC files: two 8-bit 4:2:0 grid tiles, a cropped 8-bit 4:2:0 grid tile, and an 8-bit 4:4:4 wavefront tile.
- [x] Compare complete HEIC presentation output exactly with libheif 1.23.1 and libde265 1.1.1 for single images, multi-tile grids, an auxiliary alpha image, ICC-bearing images, 4:2:0, and 4:4:4 output. Keep the exact HM native-plane tests as the independent codec-reconstruction oracle.
- [ ] Add permanent HEVC 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 fixtures covering every exposed profile and Range Extensions tool, with exact native-plane references from HM or another independent decoder.
- [x] Add permanent official HEVC 8/10/12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 fixtures with exact native-plane references and published per-plane digests. All 10 first pictures from the Sony GENERAL RExt conformance streams match every Y, Cb, and Cr sample with normal SIMD dispatch and with all hardware intrinsics disabled.
- [ ] Extend the permanent HEVC matrix to every exposed profile and each Range Extensions tool not individually isolated by the Sony GENERAL streams, with exact native-plane references from HM or another independent decoder.
- [x] Implement the shared SIMD-first YUV/CICP paths in both directions for AV1 and HEVC bit depths, chroma formats, ranges, matrix coefficients, transfer functions, color primaries, and chroma positions, with scalar fallback only after the available vector widths are exhausted.
- [ ] Complete the independently generated AV1 and HEVC conformance matrix for every supported color-signaling combination before closing the Phase 5 color exit gate.
- [x] Apply ICC conversion only after the SIMD YUV/CICP stage, alpha composition, grid assembly, and presentation transforms have produced the presented RGB image; retain ImageSharp's shared ICC converter and optimize reusable bulk kernels rather than creating a HEIF-specific color-management implementation.

6
src/ImageSharp/Formats/Heif/Hevc/HevcPictureDecoder.Prediction.cs

@ -89,6 +89,10 @@ internal sealed partial class HevcPictureDecoder
ReadOnlySpan<ushort> selectedLeft = left[..referenceLength];
if (filterReferences)
{
// Normal three-tap smoothing extends to combined 4:4:4 chroma, but strong bilinear smoothing is a luma
// operation. Separate color planes use luma syntax and therefore retain the luma behavior.
bool useStrongSmoothing = useLumaSyntax && this.sequenceParameterSet.StrongIntraSmoothingEnabled;
HevcIntraPredictor.FilterReferenceSamples(
selectedTop,
selectedLeft,
@ -96,7 +100,7 @@ internal sealed partial class HevcPictureDecoder
filteredLeft,
log2Size,
this.Picture.GetBitDepth(plane),
this.sequenceParameterSet.StrongIntraSmoothingEnabled);
useStrongSmoothing);
selectedTop = filteredTop[..referenceLength];
selectedLeft = filteredLeft[..referenceLength];

6
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraPredictorTests.cs

@ -23,7 +23,7 @@ public class HevcIntraPredictorTests
int[] modes = [0, 1, 2, 9, 18, 30, 34];
ushort[][] expected =
[
[83, 97, 110, 123, 87, 97, 108, 118, 90, 98, 105, 113, 93, 98, 103, 108],
[86, 101, 117, 132, 90, 103, 115, 128, 94, 104, 114, 123, 98, 105, 112, 119],
[84, 93, 97, 101, 88, 92, 92, 92, 90, 92, 92, 92, 91, 92, 92, 92],
[76, 82, 88, 94, 82, 88, 94, 100, 88, 94, 100, 106, 94, 100, 106, 112],
[70, 71, 71, 72, 76, 77, 77, 78, 82, 83, 83, 84, 88, 89, 89, 90],
@ -191,8 +191,8 @@ public class HevcIntraPredictorTests
{
for (int x = 0; x < size; x++)
{
int horizontal = ((size - x - 1) * left[y + 1]) + ((x + 1) * top[size]);
int vertical = ((size - y - 1) * top[x + 1]) + ((y + 1) * left[size]);
int horizontal = ((size - x - 1) * left[y + 1]) + ((x + 1) * top[size + 1]);
int vertical = ((size - y - 1) * top[x + 1]) + ((y + 1) * left[size + 1]);
destination[(y * destinationStride) + x] = (ushort)((horizontal + vertical + size) >> shift);
}
}

401
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcPictureDecoderTests.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Security.Cryptography;
using SixLabors.ImageSharp.Formats.Heif.Hevc;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
@ -11,6 +13,71 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
[Trait("Format", "Heif")]
public class HevcPictureDecoderTests
{
/// <summary>
/// Verifies the first independently coded picture from official ITU RExt conformance streams against its
/// published decoded-picture hashes.
/// </summary>
/// <param name="path">The 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="lumaDigest">The normative luma-plane MD5 digest.</param>
/// <param name="chromaBlueDigest">The normative blue-difference-plane MD5 digest, when present.</param>
/// <param name="chromaRedDigest">The normative red-difference-plane MD5 digest, when present.</param>
[Theory]
[InlineData(TestImages.Heif.General8BitMonochrome, 8, 0, "e5223be3da805fb96440dbf2bd170db0", null, null)]
[InlineData(TestImages.Heif.General8Bit420, 8, 1, "7d66d87736d627193acef745b3b7d014", "cecabae4dd685151d8de966ad01f01a8", "d069d15c457867a0a4fb9c0201eb0585")]
[InlineData(TestImages.Heif.General8Bit444, 8, 3, "2be0bad2e95b42a9f53138cd874db1c9", "f7d50f66757b468f438df47b7e6b4f36", "72d935e42f5e76aa04e0326c64f026dc")]
[InlineData(TestImages.Heif.General10Bit420, 10, 1, "9262fdf6a69587b8f1eed23c9026cb24", "cd81cc4b427565dc8c17761f2bd07c09", "0409bf573e03e2a6dd00b760b997a824")]
[InlineData(TestImages.Heif.General10Bit422, 10, 2, "4c0a0a1bf001ebf1dc440ccd9e0ae3ea", "bd35abc3f86ead4bd59e19403248ee5e", "8ce96a8885e10cda55e67eba25d9ec03")]
[InlineData(TestImages.Heif.General10Bit444, 10, 3, "d6293dfd466b7ed570beb56dee7823e3", "a82bf54ac3b2e996f40db77beff69b03", "d4f38dae50bbaa4087c7c1cf020d30a2")]
[InlineData(TestImages.Heif.General12BitMonochrome, 12, 0, "549ff2b94ede8d83bfdc64a34440817d", null, null)]
[InlineData(TestImages.Heif.General12Bit420, 12, 1, "346f709b5dfe5dd41f2ba1c70d072eb6", "6eee29326b96bb032a4a0ed822e4ba17", "59ef3982a4e0e9597d498a0d035a645a")]
[InlineData(TestImages.Heif.General12Bit422, 12, 2, "be9c8562410e42b2db985444bf8a448e", "c5d616f1ccf8b2e9f56e1bb1d3e23134", "676a9e1ad75cff3193fce58bdb2721cb")]
[InlineData(TestImages.Heif.General12Bit444, 12, 3, "057c9c3dd78c63b2689a159e21da1071", "4d0529c8e5755bb49d0ba0ec9a8e7e89", "9f5e9d559b0cf62440c2e05f141aa5d0")]
public void DecodeOfficialRangeExtensionsPictureMatchesPublishedDigest(
string path,
int bitDepth,
byte chromaFormat,
string lumaDigest,
string chromaBlueDigest,
string chromaRedDigest)
{
byte[] annexB = TestFile.Create(path).Bytes;
byte[] expectedYuv = TestFile.Create($"{path[..^4]}_frame0.yuv").Bytes;
ConvertAnnexBStillPicture(annexB, bitDepth, chromaFormat, out byte[] configurationData, out byte[] itemData);
HevcCodecConfiguration configuration = new(configurationData);
HevcImageItemBitstream bitstream = new(itemData, configuration);
using HevcPictureDecoder decoder = new(Configuration.Default, bitstream.SliceSegments[0].PictureParameterSet);
decoder.Decode(bitstream);
Assert.Equal(bitDepth, decoder.Picture.BitDepthLuma);
Assert.Equal(chromaFormat, decoder.Picture.ChromaFormat);
int expectedLength = decoder.Picture.GetWidth(HevcPlane.Y) * decoder.Picture.GetHeight(HevcPlane.Y) * (bitDepth > 8 ? 2 : 1);
if (chromaFormat != 0)
{
int chromaLength = decoder.Picture.GetWidth(HevcPlane.Cb) * decoder.Picture.GetHeight(HevcPlane.Cb) * (bitDepth > 8 ? 2 : 1);
expectedLength += chromaLength * 2;
}
Assert.Equal(expectedLength, expectedYuv.Length);
int referenceOffset = 0;
AssertCodedPlaneEqual(decoder.Picture, HevcPlane.Y, expectedYuv, ref referenceOffset);
if (chromaFormat != 0)
{
AssertCodedPlaneEqual(decoder.Picture, HevcPlane.Cb, expectedYuv, ref referenceOffset);
AssertCodedPlaneEqual(decoder.Picture, HevcPlane.Cr, expectedYuv, ref referenceOffset);
}
Assert.Equal(expectedYuv.Length, referenceOffset);
Assert.Equal(lumaDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Y));
if (chromaFormat != 0)
{
Assert.Equal(chromaBlueDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cb));
Assert.Equal(chromaRedDigest, GetPlaneDigest(decoder.Picture, HevcPlane.Cr));
}
}
/// <summary>
/// Verifies all reconstructed samples from a real HEIC grid tile against the HM reference decoder.
/// </summary>
@ -76,10 +143,49 @@ public class HevcPictureDecoderTests
int sourceY = sequenceParameterSet.ConformanceWindowTopOffset >> subsamplingY;
int width = GetDisplaySize(sequenceParameterSet.DisplayWidth, subsamplingX);
int height = GetDisplaySize(sequenceParameterSet.DisplayHeight, subsamplingY);
AssertPlaneEqual(picture, plane, sourceX, sourceY, width, height, expected, ref offset);
}
/// <summary>
/// Compares one complete coded component plane with its planar reference samples.
/// </summary>
/// <param name="picture">The decoded picture containing the component plane.</param>
/// <param name="plane">The component plane to compare.</param>
/// <param name="expected">The complete planar YUV reference.</param>
/// <param name="offset">The current reference offset, advanced past the compared plane.</param>
private static void AssertCodedPlaneEqual(HevcPictureBuffer picture, HevcPlane plane, ReadOnlySpan<byte> expected, ref int offset)
=> AssertPlaneEqual(picture, plane, 0, 0, picture.GetWidth(plane), picture.GetHeight(plane), expected, ref offset);
/// <summary>
/// Compares one rectangular component region with its planar reference samples.
/// </summary>
/// <param name="picture">The decoded picture containing the component plane.</param>
/// <param name="plane">The component plane to compare.</param>
/// <param name="sourceX">The source-region X coordinate in component samples.</param>
/// <param name="sourceY">The source-region Y coordinate in component samples.</param>
/// <param name="width">The compared width in component samples.</param>
/// <param name="height">The compared height in component samples.</param>
/// <param name="expected">The complete planar YUV reference.</param>
/// <param name="offset">The current reference offset, advanced past the compared plane.</param>
private static void AssertPlaneEqual(
HevcPictureBuffer picture,
HevcPlane plane,
int sourceX,
int sourceY,
int width,
int height,
ReadOnlySpan<byte> expected,
ref int offset)
{
bool usesHighBitDepthSamples = picture.GetBitDepth(plane) > 8;
int mismatchCount = 0;
int maximumDifference = 0;
int firstMismatchX = 0;
int firstMismatchY = 0;
int minimumMismatchX = width;
int minimumMismatchY = height;
int maximumMismatchX = 0;
int maximumMismatchY = 0;
ushort firstActual = 0;
ushort firstExpected = 0;
for (int y = 0; y < height; y++)
@ -87,7 +193,17 @@ public class HevcPictureDecoderTests
Span<ushort> actualRow = picture.GetRowSpan(plane, sourceY + y).Slice(sourceX, width);
for (int x = 0; x < width; x++)
{
ushort expectedSample = expected[offset++];
ushort expectedSample;
if (usesHighBitDepthSamples)
{
expectedSample = BinaryPrimitives.ReadUInt16LittleEndian(expected[offset..]);
offset += 2;
}
else
{
expectedSample = expected[offset++];
}
int difference = Math.Abs(actualRow[x] - expectedSample);
if (difference == 0)
{
@ -104,13 +220,18 @@ public class HevcPictureDecoderTests
mismatchCount++;
maximumDifference = Math.Max(maximumDifference, difference);
minimumMismatchX = Math.Min(minimumMismatchX, x);
minimumMismatchY = Math.Min(minimumMismatchY, y);
maximumMismatchX = Math.Max(maximumMismatchX, x);
maximumMismatchY = Math.Max(maximumMismatchY, y);
}
}
Assert.True(
mismatchCount == 0,
$"{plane} contained {mismatchCount} differing samples. The maximum difference was {maximumDifference}; " +
$"the first mismatch at ({firstMismatchX}, {firstMismatchY}) was {firstActual}, expected {firstExpected}.");
$"the mismatches span ({minimumMismatchX}, {minimumMismatchY}) through ({maximumMismatchX}, {maximumMismatchY}), " +
$"and the first mismatch at ({firstMismatchX}, {firstMismatchY}) was {firstActual}, expected {firstExpected}.");
}
/// <summary>
@ -120,4 +241,280 @@ public class HevcPictureDecoderTests
/// <param name="subsampling">The component subsampling shift.</param>
/// <returns>The displayed component extent.</returns>
private static int GetDisplaySize(int lumaSize, int subsampling) => (lumaSize + (1 << subsampling) - 1) >> subsampling;
/// <summary>
/// Adapts the first independently coded Annex B picture to the bounded <c>hvc1</c> item contract used by the
/// production decoder.
/// </summary>
/// <param name="annexB">The complete official conformance stream.</param>
/// <param name="bitDepth">The stream's published component precision.</param>
/// <param name="chromaFormat">The stream's published chroma-format identifier.</param>
/// <param name="configurationData">The generated item-local HEVC decoder configuration.</param>
/// <param name="itemData">The generated length-delimited payload containing only the first picture.</param>
private static void ConvertAnnexBStillPicture(
ReadOnlySpan<byte> annexB,
int bitDepth,
byte chromaFormat,
out byte[] configurationData,
out byte[] itemData)
{
const int VideoParameterSetNalUnitType = 32;
const int SequenceParameterSetNalUnitType = 33;
const int PictureParameterSetNalUnitType = 34;
const int HighestVideoCodingLayerNalUnitType = 31;
const int NalUnitHeaderLength = 2;
const int SpsConfigurationPrefixLength = 13;
const int ProfileTierLevelLength = 12;
const int ConfigurationHeaderLength = 23;
const int ParameterSetArrayHeaderLength = 5;
const int ParameterSetCount = 3;
const int ItemNalUnitLengthFieldLength = 4;
(int Offset, int Length) videoParameterSet = default;
(int Offset, int Length) sequenceParameterSet = default;
(int Offset, int Length) pictureParameterSet = default;
List<(int Offset, int Length)> pictureNalUnits = [];
int offset = 0;
bool foundPicture = false;
while (TryReadAnnexBNalUnit(annexB, ref offset, out int nalOffset, out int nalLength))
{
// HEVC stores nal_unit_type in the six bits following forbidden_zero_bit.
int nalUnitType = (annexB[nalOffset] >> 1) & 0x3F;
if (!foundPicture)
{
switch (nalUnitType)
{
case VideoParameterSetNalUnitType:
videoParameterSet = (nalOffset, nalLength);
break;
case SequenceParameterSetNalUnitType:
sequenceParameterSet = (nalOffset, nalLength);
break;
case PictureParameterSetNalUnitType:
pictureParameterSet = (nalOffset, nalLength);
break;
}
}
if (nalUnitType > HighestVideoCodingLayerNalUnitType)
{
continue;
}
// The first RBSP bit after the two-byte NAL header is first_slice_segment_in_pic_flag. No emulation byte
// can precede that first bit, so it can terminate the extracted picture without parsing later sequences.
bool firstSliceSegment = (annexB[nalOffset + NalUnitHeaderLength] & 0x80) != 0;
if (foundPicture && firstSliceSegment)
{
break;
}
foundPicture = true;
pictureNalUnits.Add((nalOffset, nalLength));
}
Assert.True(videoParameterSet.Length > 0, "The conformance stream does not contain a VPS before its first picture.");
Assert.True(sequenceParameterSet.Length > 0, "The conformance stream does not contain an SPS before its first picture.");
Assert.True(pictureParameterSet.Length > 0, "The conformance stream does not contain a PPS before its first picture.");
Assert.NotEmpty(pictureNalUnits);
ReadOnlySpan<byte> sps = annexB.Slice(sequenceParameterSet.Offset, sequenceParameterSet.Length);
Span<byte> spsRbspPrefix = stackalloc byte[SpsConfigurationPrefixLength];
CopyRbspPrefix(sps[NalUnitHeaderLength..], spsRbspPrefix);
int configurationLength = ConfigurationHeaderLength
+ (ParameterSetCount * ParameterSetArrayHeaderLength)
+ videoParameterSet.Length
+ sequenceParameterSet.Length
+ pictureParameterSet.Length;
configurationData = new byte[configurationLength];
// ISO/IEC 14496-15 defines a fixed 23-byte HEVCDecoderConfigurationRecord header. Copying
// profile_tier_level directly from the published SPS avoids synthesizing codec capability claims.
configurationData[0] = 1; // configurationVersion
spsRbspPrefix.Slice(1, ProfileTierLevelLength).CopyTo(configurationData.AsSpan(1, ProfileTierLevelLength));
configurationData[13] = 0xF0; // reserved and min_spatial_segmentation_idc = 0
configurationData[15] = 0xFC; // reserved and parallelismType = 0
configurationData[16] = (byte)(0xFC | chromaFormat); // reserved and chromaFormat
configurationData[17] = (byte)(0xF8 | (bitDepth - 8)); // reserved and bitDepthLumaMinus8
configurationData[18] = (byte)(0xF8 | (chromaFormat == 0 ? 0 : bitDepth - 8)); // reserved and bitDepthChromaMinus8
int maxSubLayers = ((spsRbspPrefix[0] >> 1) & 7) + 1;
int temporalIdNesting = spsRbspPrefix[0] & 1;
configurationData[21] = (byte)((maxSubLayers << 3) | (temporalIdNesting << 2) | 3); // lengthSizeMinusOne = 3
configurationData[22] = ParameterSetCount;
int configurationOffset = ConfigurationHeaderLength;
WriteParameterSetArray(configurationData, ref configurationOffset, VideoParameterSetNalUnitType, annexB.Slice(videoParameterSet.Offset, videoParameterSet.Length));
WriteParameterSetArray(configurationData, ref configurationOffset, SequenceParameterSetNalUnitType, sps);
WriteParameterSetArray(configurationData, ref configurationOffset, PictureParameterSetNalUnitType, annexB.Slice(pictureParameterSet.Offset, pictureParameterSet.Length));
Assert.Equal(configurationData.Length, configurationOffset);
int itemLength = 0;
foreach ((int _, int nalLength) in pictureNalUnits)
{
itemLength += ItemNalUnitLengthFieldLength + nalLength;
}
itemData = new byte[itemLength];
int itemOffset = 0;
foreach ((int nalOffset, int nalLength) in pictureNalUnits)
{
BinaryPrimitives.WriteUInt32BigEndian(itemData.AsSpan(itemOffset), (uint)nalLength);
itemOffset += ItemNalUnitLengthFieldLength;
annexB.Slice(nalOffset, nalLength).CopyTo(itemData.AsSpan(itemOffset));
itemOffset += nalLength;
}
}
/// <summary>
/// Reads the next NAL-unit payload from an Annex B byte stream.
/// </summary>
/// <param name="source">The complete Annex B byte stream.</param>
/// <param name="offset">The current search offset, advanced to the next start code.</param>
/// <param name="nalOffset">The returned NAL-unit payload offset.</param>
/// <param name="nalLength">The returned NAL-unit payload length.</param>
/// <returns><see langword="true"/> when another complete NAL unit was found.</returns>
private static bool TryReadAnnexBNalUnit(ReadOnlySpan<byte> source, ref int offset, out int nalOffset, out int nalLength)
{
int startCodeOffset = FindAnnexBStartCode(source, offset, out int startCodeLength);
if (startCodeOffset < 0)
{
nalOffset = 0;
nalLength = 0;
return false;
}
nalOffset = startCodeOffset + startCodeLength;
int nextStartCodeOffset = FindAnnexBStartCode(source, nalOffset, out _);
int nalEnd = nextStartCodeOffset < 0 ? source.Length : nextStartCodeOffset;
// Annex B permits trailing_zero_8bits between a NAL unit and the next start-code prefix. They are byte-stream
// framing and must not enter the length-delimited item payload.
while (nalEnd > nalOffset && source[nalEnd - 1] == 0)
{
nalEnd--;
}
offset = nextStartCodeOffset < 0 ? source.Length : nextStartCodeOffset;
nalLength = nalEnd - nalOffset;
return nalLength >= 2;
}
/// <summary>
/// Locates the next three- or four-byte Annex B start code.
/// </summary>
/// <param name="source">The complete Annex B byte stream.</param>
/// <param name="offset">The first byte to inspect.</param>
/// <param name="length">The returned start-code length.</param>
/// <returns>The start-code offset, or negative one when no code remains.</returns>
private static int FindAnnexBStartCode(ReadOnlySpan<byte> source, int offset, out int length)
{
for (int index = offset; index <= source.Length - 3; index++)
{
if (source[index] != 0 || source[index + 1] != 0)
{
continue;
}
if (source[index + 2] == 1)
{
length = 3;
return index;
}
if (index <= source.Length - 4 && source[index + 2] == 0 && source[index + 3] == 1)
{
length = 4;
return index;
}
}
length = 0;
return -1;
}
/// <summary>
/// Copies the fixed SPS prefix through general_level_idc while removing emulation-prevention bytes.
/// </summary>
/// <param name="escapedRbsp">The SPS bytes following the NAL-unit header.</param>
/// <param name="destination">The fixed 13-byte SPS prefix destination.</param>
private static void CopyRbspPrefix(ReadOnlySpan<byte> escapedRbsp, Span<byte> destination)
{
const int EscapeZeroCount = 2;
const byte EmulationPreventionByte = 3;
int sourceOffset = 0;
int destinationOffset = 0;
int consecutiveZeroes = 0;
while (destinationOffset < destination.Length)
{
byte value = escapedRbsp[sourceOffset++];
if (consecutiveZeroes == EscapeZeroCount && value == EmulationPreventionByte)
{
consecutiveZeroes = 0;
continue;
}
destination[destinationOffset++] = value;
consecutiveZeroes = value == 0 ? consecutiveZeroes + 1 : 0;
}
}
/// <summary>
/// Writes one complete parameter-set array to an HEVC decoder-configuration record.
/// </summary>
/// <param name="configuration">The complete configuration destination.</param>
/// <param name="offset">The current destination offset, advanced past the array.</param>
/// <param name="nalUnitType">The parameter-set NAL-unit type.</param>
/// <param name="nalUnit">The complete NAL unit without Annex B framing.</param>
private static void WriteParameterSetArray(Span<byte> configuration, ref int offset, byte nalUnitType, ReadOnlySpan<byte> nalUnit)
{
// Each complete array contains exactly one parameter set from the source stream. ISO/IEC 14496-15 stores
// array_completeness in the high bit and the six-bit HEVC NAL-unit type in the low bits.
configuration[offset++] = (byte)(0x80 | nalUnitType);
BinaryPrimitives.WriteUInt16BigEndian(configuration[offset..], 1);
offset += 2;
BinaryPrimitives.WriteUInt16BigEndian(configuration[offset..], (ushort)nalUnit.Length);
offset += 2;
nalUnit.CopyTo(configuration[offset..]);
offset += nalUnit.Length;
}
/// <summary>
/// Calculates the HEVC decoded-picture MD5 digest for one reconstructed component plane.
/// </summary>
/// <param name="picture">The reconstructed picture.</param>
/// <param name="plane">The component plane to hash.</param>
/// <returns>The lowercase hexadecimal decoded-picture digest.</returns>
private static string GetPlaneDigest(HevcPictureBuffer picture, HevcPlane plane)
{
int width = picture.GetWidth(plane);
int height = picture.GetHeight(plane);
int bytesPerSample = picture.GetBitDepth(plane) > 8 ? 2 : 1;
byte[] rowBytes = new byte[width * bytesPerSample];
using IncrementalHash hash = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
for (int y = 0; y < height; y++)
{
Span<ushort> samples = picture.GetRowSpan(plane, y)[..width];
if (bytesPerSample == 1)
{
for (int x = 0; x < width; x++)
{
rowBytes[x] = (byte)samples[x];
}
}
else
{
for (int x = 0; x < width; x++)
{
BinaryPrimitives.WriteUInt16LittleEndian(rowBytes.AsSpan(x * 2), samples[x]);
}
}
hash.AppendData(rowBytes);
}
return Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
}
}

10
tests/ImageSharp.Tests/TestImages.cs

@ -1294,6 +1294,16 @@ public static class TestImages
public const string DwsampleTileHvcConfiguration = "Heif/Hevc/dwsample-tile.hvcc";
public const string DwsampleTilePayload = "Heif/Hevc/dwsample-tile.hvc1";
public const string DwsampleTileReferenceYuv = "Heif/Hevc/dwsample-tile-hm.yuv";
public const string General8BitMonochrome = "Heif/Hevc/Conformance/GENERAL_8b_400_RExt_Sony_1.bit";
public const string General8Bit420 = "Heif/Hevc/Conformance/GENERAL_8b_420_RExt_Sony_1.bit";
public const string General8Bit444 = "Heif/Hevc/Conformance/GENERAL_8b_444_RExt_Sony_2.bit";
public const string General10Bit420 = "Heif/Hevc/Conformance/GENERAL_10b_420_RExt_Sony_1.bit";
public const string General10Bit422 = "Heif/Hevc/Conformance/GENERAL_10b_422_RExt_Sony_1.bit";
public const string General10Bit444 = "Heif/Hevc/Conformance/GENERAL_10b_444_RExt_Sony_2.bit";
public const string General12BitMonochrome = "Heif/Hevc/Conformance/GENERAL_12b_400_RExt_Sony_1.bit";
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 Image1 = "Heif/image1.heic";
public const string Image2 = "Heif/image2.heic";
public const string Image3 = "Heif/image3.heic";

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_420_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:de0f153bde98066c6d7368a51e0907cc9a829b34a6b998672999ce0b283cb212
size 30436

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_420_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d9b784954f44b512caef67c967c873ed469643f0fe03b62b67fa00abc8c74fa3
size 460800

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_422_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a6c012634574009542d4046b24a2c29fbabaa7351a62b19300df5eec3336e810
size 71890

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_422_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:210797850418d6544948c375f65d34e8a58000d52a868c24088701691f6f2573
size 614400

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_444_RExt_Sony_2.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ab9c7d7c0554e6342836fa0212e6334ecc894141f1cb90cd4512c108dabcb936
size 510412

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_10b_444_RExt_Sony_2_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e2af46b75d97462b7fd8ab67238705747fe74571efcb564ff13468c42ae1158
size 921600

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_400_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:42ba06081165645c28b1f38ca1addd62eaf48925bb64e3439b404f32a2b50952
size 38232

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_400_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:01880b5616174d54d5030ce8726374d00242158bba7b3b312141eb48be2625e4
size 307200

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3b1f3e683bc1c6b6abcaa26fe540d6ec26a3734255010b27ae650b44b52654e9
size 57176

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_420_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c394390723d177e5421eac9428efcb2ead2d94af967aa1937572b689095ac850
size 460800

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3f534275c0c3d317a3dafc30a3e0eb89d3e7d6aa2f2c0df0e9d27712354700d
size 139893

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_422_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0b5930a097a0acd815b1b6575d7cbbf9c9921b49cb3cfb12f46026174fc6a173
size 614400

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:197023ccc20fd340c108646fd06b08b92fc29a613609714f6983e15d3770d669
size 1185165

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_12b_444_RExt_Sony_2_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:751dd2deb76632024b523289d945e3b3fb32d27d1eac22bf1de0b8c2ff9ebbc9
size 921600

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_400_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6151ab4b8fdc2a8643d169f98d9878e8221f9d23d97c71447bd4d7de5e79bd4b
size 10349

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_400_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f5ca4e944cc21944edbf1507e2a1a178eb878b31f0f05eeb65a1d3a81aae3dbf
size 153600

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_420_RExt_Sony_1.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:44b43ba732bae1b05c217661f7ea9049a52288bd5e4a966252b44406122b6ea4
size 14092

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_420_RExt_Sony_1_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3503c56768f9de92a7dd6debbd959ed4636ea1c9e35b767d5f881dee42a572a6
size 230400

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_444_RExt_Sony_2.bit

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ef4e0d887ad0a02e1f8218e3fbf93258c1b1e3923d079296df8df69fac56e538
size 258456

3
tests/Images/Input/Heif/Hevc/Conformance/GENERAL_8b_444_RExt_Sony_2_frame0.yuv

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6974f93431618d623c8e9168ea259b5280e37dd22fd059eb5b2e25318de61ff7
size 460800

9
tests/Images/Input/Heif/Hevc/Conformance/README.md

@ -0,0 +1,9 @@
# HEVC Range Extensions conformance fixtures
These fixtures come from the electronic attachment to [ITU-T H.265.1 (10/2018)](https://www.itu.int/rec/T-REC-H.265.1-201810-I/en), the conformance specification aligned with ISO/IEC 23008-8. Table 4 identifies the Sony `GENERAL_*_RExt` streams and their required profiles.
The `.bit` files are the original Annex B conformance streams. Each `_frame0.yuv` file contains the first decoded picture extracted byte-for-byte from the corresponding published planar reference output; it was not generated by ImageSharp.
`HevcPictureDecoderTests.DecodeOfficialRangeExtensionsPictureMatchesPublishedDigest` limits each stream to its first independently coded picture, adapts that picture to the bounded `hvc1` item contract, compares every reconstructed sample with `_frame0.yuv`, and verifies the published decoded-picture MD5 value for every present plane.
The retained matrix covers the ImageSharp 8, 10, and 12-bit still-image precision boundary across monochrome, 4:2:0, 4:2:2, and 4:4:4. Tool-specific H.265.1 streams remain necessary before every exposed Range Extensions tool can be marked complete.
Loading…
Cancel
Save