Browse Source

Parse HEVC sequence parameter sets

pull/2633/head
James Jackson-South 1 week ago
parent
commit
939718daac
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 40
      src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
  3. 123
      src/ImageSharp/Formats/Heif/Hevc/HevcParameterSetSyntax.cs
  4. 250
      src/ImageSharp/Formats/Heif/Hevc/HevcScalingList.cs
  5. 464
      src/ImageSharp/Formats/Heif/Hevc/HevcSequenceParameterSet.cs
  6. 178
      src/ImageSharp/Formats/Heif/Hevc/HevcShortTermReferencePictureSet.cs
  7. 97
      src/ImageSharp/Formats/Heif/Hevc/HevcVideoParameterSet.cs
  8. 247
      src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs

1
HEIF_IMPLEMENTATION_PLAN.md

@ -114,6 +114,7 @@ This snapshot pins or classifies the available references and failures; it does
| `HevcCodecConfiguration`, `HevcNalUnitArray`, and `HeifItem.HevcCodecConfiguration` | ISO/IEC 14496-15 `HEVCDecoderConfigurationRecord`, HEVC NAL-unit header syntax, and HEVC image-item configuration requirements | HM `source/Lib/TLibDecoder/NALread.cpp` and parameter-header paths at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` v1.6.0 decoder NAL and parameter-header paths at `c83a76b084498d55f252f48b2e3786804cdf24b7`; independently produced HEIC fixtures for `hvcC` record layout | Associate exactly one `hvcC` property with each `hvc1` image item; validate the fixed profile, level, chroma, bit-depth, reserved, length-size, array, and NAL-header fields within the property boundary; and retain only the image description plus bounded configuration NAL units required to decode that item. Consume but do not retain average-frame-rate or temporal-layer presentation fields. Do not add visual sample entries, sample descriptions, tracks, timing, access-unit timelines, or a generic decoder-configuration model. BSD and Apache-2.0 notice preservation and separate HEVC patent/release clearance remain final integration gates. |
| `HevcNalUnit`, `HevcNalUnitHeader`, `HevcRbspDecoder`, and `HevcBitReader` | HEVC sections 7.3.1.1 and 7.3.1.2 NAL-unit and header syntax, section 7.3.2.11 RBSP trailing bits, and section 9.1 Exp-Golomb parsing | HM `source/Lib/TLibDecoder/NALread.cpp` and `SyntaxElementParser.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` NAL extraction and bitstream paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Validate the fixed NAL header, remove only legal emulation-prevention bytes, and provide bounded fixed-width, flag, unsigned/signed Exp-Golomb, and RBSP-trailing-bit reads. Configuration arrays immediately use the shared parser. The primitives consume only NAL units delivered by a supported HEIF image item or its `hvcC` property and introduce no Annex B API, access-unit model, timeline, track, sample table, or generic ISO BMFF surface. |
| `HevcProfileTierLevel`, `HevcVideoParameterSet`, and `HevcCodecConfiguration.VideoParameterSets` | HEVC sections 7.3.2.1 video parameter sets, 7.3.3 profile-tier-level syntax, and E.2.2 hypothetical-reference-decoder syntax; ISO/IEC 14496-15 `HEVCDecoderConfigurationRecord` matching semantics | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` functions `parseVPS`, `parsePTL`, `parseProfileTier`, and `parseHrdParameters` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` profile-tier-level and VPS paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Retain the base-layer VPS identifier, temporal-sublayer count, nesting flag, and exact general profile/tier/constraint/level fields. Validate profile, tier, compatibility, level, temporal count, and nesting against `hvcC`, while tolerating the independently observed legacy-muxer practice of zeroing some or all profile-specific constraint bits in the configuration record; SPS validation remains authoritative for chroma and bit depth. Consume but do not retain sublayer ordering, timing, and HRD syntax. Reject multilayer and layer-set state because the supported contract is one independently coded image item; add no layer-selection, access-unit, DPB, scheduling, track, or sample model. |
| `HevcSequenceParameterSet`, `HevcVideoUsabilityInformation`, `HevcScalingList`, `HevcShortTermReferencePictureSet`, and `HevcParameterSetSyntax` | HEVC sections 7.3.2.2 sequence parameter sets, 7.3.4 scaling-list data, 7.3.7 short-term reference-picture sets, E.2.1 VUI syntax, and Range Extensions SPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` functions `parseSPS`, `parseScalingList`, `xDecodeScalingList`, `parseShortTermRefPicSet`, `parseVUI`, and `parseHrdParameters`, plus `source/Lib/TLibCommon/TComRom.cpp` default matrices and diagonal scans, at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` SPS, VUI, scaling-list, and reference-set paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Retain coded/display dimensions, conformance crop, monochrome/4:2:0/4:2:2/4:4:4 and separate-plane layout, 8-through-16-bit precision, coding/transform/PCM geometry, effective scaling matrices, compression-tool flags, bounded reference-set declarations, Range Extensions tools, and still-image VUI aspect/color/range/chroma-location/display-window fields. Consume timing, HRD, ordering, and bitstream-restriction syntax without scheduling state. Reject interlaced fields and multilayer extensions because they do not represent the one progressive independently coded image-item contract; add no DPB pictures, playback state, tracks, or sample tables. |
| `HeifContentColorVolume`, `HeifItem.ContentColorVolume`, and `HeifDecoderCore` content color-volume parsing and presentation | HEIF content color-volume item property; AVIF 1.2 content color-volume requirements; ITU-T H.274 (V4) content colour volume syntax and semantics | libavif `src/read.c` function `avifSkipContentColourVolume` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode only the bounded per-image `cclv` property: require zero cancellation, persistence, and reserved bits; preserve optional signed G/B/R primary coordinates and normalized minimum, maximum, and average luminance values; and validate their registered ranges and ordering. Expose the effective grid-or-tile still-image value through `HeifMetadata`. Do not add SEI persistence, retained video state, tracks, samples, timing, or a generic ISO BMFF color-volume box model. |
| `HeifAmbientViewingEnvironment`, `HeifReferenceViewingEnvironment`, `HeifNominalDiffuseWhite`, and their per-item presentation metadata | ISOBMFF ambient viewing environment; ITU-T H.274 (V4) section 8.13; HEIF Amendment 1 sections 6.5.44 and 6.5.45; AVIF 1.2 image-item box requirements | libavif `src/read.c` functions `avifSkipAmbientViewingEnvironment`, `avifSkipReferenceViewingEnvironment`, and `avifSkipNominalDiffuseWhite` at `092276ce89098ead06db80975173191e5fee1826`; official ITU-T H.274 (V4), January 2026 | Decode the fixed-size `amve`, version-zero `reve`, and version-zero `ndwt` properties only when associated with the presented still-image item. Preserve physical illuminance and luminance units, distinct surround/periphery chromaticities, and the coded zero that requests the standard nominal diffuse-white default. Validate registered coordinate ranges and duplicate associations. Retain no video-SEI persistence, visual sample entry, display pipeline, track, timing, or generic viewing-environment box model. |
| `GridHeifItemDecoder` and `HeifDecoderCore` grid/thumbnail selection | ISO/IEC 23008-12 section 6.6.2.3 image-grid syntax and MIAF grid-cell constraints | libavif `src/read.c` functions `avifParseImageGridBox`, `avifDecoderDataAllocateImagePlanes`, and `avifDecoderDataCopyTileToImage` at `092276ce89098ead06db80975173191e5fee1826` | Parse version-zero 16-bit and 32-bit grid descriptors, preserve row-major `dimg` order, require the declared tile count and one coding format, validate canvas coverage and edge overlap, and crop only the rightmost column and bottom row while copying through ImageSharp pixel buffers. A primary grid whose tile codec is unavailable may use only a decodable thumbnail that explicitly references that grid. |

40
src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs

@ -164,6 +164,41 @@ internal sealed class HevcCodecConfiguration
}
this.VideoParameterSets = videoParameterSets;
List<HevcSequenceParameterSet> sequenceParameterSets = new();
foreach (HevcNalUnitArray nalUnitArray in this.nalUnitArrays)
{
const byte sequenceParameterSetNalUnitType = 33;
if (nalUnitArray.NalUnitType != sequenceParameterSetNalUnitType)
{
continue;
}
foreach (HevcNalUnit nalUnit in nalUnitArray.NalUnits)
{
HevcSequenceParameterSet sequenceParameterSet = new(nalUnit);
bool referencesKnownVideoParameterSet = false;
foreach (HevcVideoParameterSet videoParameterSet in videoParameterSets)
{
referencesKnownVideoParameterSet |= videoParameterSet.Id == sequenceParameterSet.VideoParameterSetId;
}
if (!referencesKnownVideoParameterSet
|| !sequenceParameterSet.ProfileTierLevel.Matches(this)
|| sequenceParameterSet.ChromaFormat != this.ChromaFormat
|| sequenceParameterSet.BitDepthLuma != this.BitDepthLuma
|| sequenceParameterSet.BitDepthChroma != this.BitDepthChroma
|| (temporalLayerCount != 0 && sequenceParameterSet.MaxSubLayers != temporalLayerCount)
|| (temporalLayerCount != 0 && sequenceParameterSet.TemporalIdNestingFlag != temporalIdNested))
{
throw new InvalidImageContentException("The HEVC sequence parameter set does not match its codec configuration.");
}
sequenceParameterSets.Add(sequenceParameterSet);
}
}
this.SequenceParameterSets = sequenceParameterSets;
}
/// <summary>
@ -237,6 +272,11 @@ internal sealed class HevcCodecConfiguration
/// </summary>
public IReadOnlyList<HevcVideoParameterSet> VideoParameterSets { get; }
/// <summary>
/// Gets the validated sequence parameter sets carried by the codec-configuration property.
/// </summary>
public IReadOnlyList<HevcSequenceParameterSet> SequenceParameterSets { get; }
/// <summary>
/// Validates the associated pixel-information property against the coded luma and chroma sample precisions.
/// </summary>

123
src/ImageSharp/Formats/Heif/Hevc/HevcParameterSetSyntax.cs

@ -0,0 +1,123 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Provides shared bounded syntax operations used by HEVC parameter-set readers.
/// </summary>
internal static class HevcParameterSetSyntax
{
/// <summary>
/// Gets the horizontal conformance-window unit for an HEVC chroma layout.
/// </summary>
/// <param name="chromaFormat">The chroma-format identifier.</param>
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
/// <returns>The horizontal unit in luma samples.</returns>
public static int GetCropUnitWidth(byte chromaFormat, bool separateColorPlane)
=> !separateColorPlane && chromaFormat is 1 or 2 ? 2 : 1;
/// <summary>
/// Gets the vertical conformance-window unit for an HEVC chroma layout.
/// </summary>
/// <param name="chromaFormat">The chroma-format identifier.</param>
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate color planes.</param>
/// <returns>The vertical unit in luma samples.</returns>
public static int GetCropUnitHeight(byte chromaFormat, bool separateColorPlane)
=> !separateColorPlane && chromaFormat == 1 ? 2 : 1;
/// <summary>
/// Consumes hypothetical-reference-decoder syntax without adding playback state to the still-image model.
/// </summary>
/// <param name="reader">The parameter-set raw byte sequence payload reader.</param>
/// <param name="commonInformationPresent">Whether common HRD flags are coded for this parameter set.</param>
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
/// <param name="nalHrdParametersPresent">The effective NAL HRD presence flag.</param>
/// <param name="vclHrdParametersPresent">The effective VCL HRD presence flag.</param>
/// <param name="subPictureHrdParametersPresent">The effective sub-picture HRD presence flag.</param>
/// <exception cref="InvalidImageContentException">The HRD syntax is truncated or exceeds its registered bounds.</exception>
public static void SkipHrdParameters(
ref HevcBitReader reader,
bool commonInformationPresent,
int maxSubLayersMinusOne,
ref bool nalHrdParametersPresent,
ref bool vclHrdParametersPresent,
ref bool subPictureHrdParametersPresent)
{
if (commonInformationPresent)
{
nalHrdParametersPresent = reader.ReadFlag();
vclHrdParametersPresent = reader.ReadFlag();
subPictureHrdParametersPresent = false;
if (nalHrdParametersPresent || vclHrdParametersPresent)
{
subPictureHrdParametersPresent = reader.ReadFlag();
if (subPictureHrdParametersPresent)
{
reader.ReadBits(8);
reader.ReadBits(5);
reader.ReadFlag();
reader.ReadBits(5);
}
reader.ReadBits(4);
reader.ReadBits(4);
if (subPictureHrdParametersPresent)
{
reader.ReadBits(4);
}
reader.ReadBits(5);
reader.ReadBits(5);
reader.ReadBits(5);
}
}
for (int subLayer = 0; subLayer <= maxSubLayersMinusOne; subLayer++)
{
bool fixedPictureRateGeneral = reader.ReadFlag();
bool fixedPictureRateWithinCvs = fixedPictureRateGeneral || reader.ReadFlag();
bool lowDelayHrd = false;
if (fixedPictureRateWithinCvs)
{
reader.ReadUnsignedExpGolomb();
}
else
{
lowDelayHrd = reader.ReadFlag();
}
uint cpbCountMinusOne = 0;
if (!lowDelayHrd)
{
cpbCountMinusOne = reader.ReadUnsignedExpGolomb();
if (cpbCountMinusOne > 31)
{
throw new InvalidImageContentException("The HEVC HRD syntax declares too many coded-picture buffers.");
}
}
for (int hrdKind = 0; hrdKind < 2; hrdKind++)
{
bool parametersPresent = hrdKind == 0 ? nalHrdParametersPresent : vclHrdParametersPresent;
if (!parametersPresent)
{
continue;
}
for (uint cpbIndex = 0; cpbIndex <= cpbCountMinusOne; cpbIndex++)
{
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
if (subPictureHrdParametersPresent)
{
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
}
reader.ReadFlag();
}
}
}
}
}

250
src/ImageSharp/Formats/Heif/Hevc/HevcScalingList.cs

@ -0,0 +1,250 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Contains the HEVC quantization scaling matrices and their large-transform DC coefficients.
/// </summary>
internal sealed class HevcScalingList
{
/// <summary>
/// The number of matrix identifiers defined for each transform-size category.
/// </summary>
private const int MatrixCount = 6;
/// <summary>
/// The flat default matrix used by four-by-four transforms.
/// </summary>
private static readonly byte[] Default4x4 =
[
16, 16, 16, 16,
16, 16, 16, 16,
16, 16, 16, 16,
16, 16, 16, 16,
];
/// <summary>
/// The default intra-predicted matrix used by transforms of eight-by-eight and larger.
/// </summary>
private static readonly byte[] DefaultIntra8x8 =
[
16, 16, 16, 16, 17, 18, 21, 24,
16, 16, 16, 16, 17, 19, 22, 25,
16, 16, 17, 18, 20, 22, 25, 29,
16, 16, 18, 21, 24, 27, 31, 36,
17, 17, 20, 24, 30, 35, 41, 47,
18, 19, 22, 27, 35, 44, 54, 65,
21, 22, 25, 31, 41, 54, 70, 88,
24, 25, 29, 36, 47, 65, 88, 115,
];
/// <summary>
/// The default inter-predicted matrix used by transforms of eight-by-eight and larger.
/// </summary>
private static readonly byte[] DefaultInter8x8 =
[
16, 16, 16, 16, 17, 18, 20, 24,
16, 16, 16, 17, 18, 20, 24, 25,
16, 16, 17, 18, 20, 24, 25, 28,
16, 17, 18, 20, 24, 25, 28, 33,
17, 18, 20, 24, 25, 28, 33, 41,
18, 20, 24, 25, 28, 33, 41, 54,
20, 24, 25, 28, 33, 41, 54, 71,
24, 25, 28, 33, 41, 54, 71, 91,
];
/// <summary>
/// The diagonal coefficient order for four-by-four matrices.
/// </summary>
private static readonly byte[] DiagonalScan4x4 = CreateDiagonalScan(4);
/// <summary>
/// The diagonal coefficient order for matrices of eight-by-eight and larger.
/// </summary>
private static readonly byte[] DiagonalScan8x8 = CreateDiagonalScan(8);
/// <summary>
/// The decoded matrices, indexed by transform-size category and matrix identifier.
/// </summary>
private readonly byte[][] matrices = new byte[4 * MatrixCount][];
/// <summary>
/// The DC coefficients for sixteen-by-sixteen and thirty-two-by-thirty-two matrices.
/// </summary>
private readonly byte[] dcCoefficients = new byte[4 * MatrixCount];
/// <summary>
/// Initializes a new instance of the <see cref="HevcScalingList"/> class with the normative default matrices.
/// </summary>
public HevcScalingList()
{
for (int sizeId = 0; sizeId < 4; sizeId++)
{
int coefficientCount = sizeId == 0 ? 16 : 64;
for (int matrixId = 0; matrixId < MatrixCount; matrixId++)
{
byte[] matrix = new byte[coefficientCount];
ReadOnlySpan<byte> source = sizeId == 0
? Default4x4
: matrixId < 3 ? DefaultIntra8x8 : DefaultInter8x8;
source.CopyTo(matrix);
this.matrices[GetIndex(sizeId, matrixId)] = matrix;
this.dcCoefficients[GetIndex(sizeId, matrixId)] = 16;
}
}
}
/// <summary>
/// Reads a complete scaling-list-data structure.
/// </summary>
/// <param name="reader">The parameter-set raw byte sequence payload reader.</param>
/// <returns>The decoded scaling matrices.</returns>
/// <exception cref="InvalidImageContentException">A prediction reference is outside its permitted matrix set.</exception>
public static HevcScalingList Parse(ref HevcBitReader reader)
{
HevcScalingList scalingList = new();
for (int sizeId = 0; sizeId < 4; sizeId++)
{
int matrixStep = sizeId == 3 ? 3 : 1;
for (int matrixId = 0; matrixId < MatrixCount; matrixId += matrixStep)
{
int destinationIndex = GetIndex(sizeId, matrixId);
bool predictionMode = reader.ReadFlag();
if (!predictionMode)
{
uint matrixIdDelta = reader.ReadUnsignedExpGolomb();
if (sizeId == 3)
{
if (matrixIdDelta > matrixId / 3)
{
throw new InvalidImageContentException("The HEVC scaling list references an unavailable matrix.");
}
matrixIdDelta *= 3;
}
if (matrixIdDelta > matrixId)
{
throw new InvalidImageContentException("The HEVC scaling list references an unavailable matrix.");
}
int referenceMatrixId = matrixId - (int)matrixIdDelta;
if (referenceMatrixId != matrixId)
{
int referenceIndex = GetIndex(sizeId, referenceMatrixId);
// Span copying uses ImageSharp's runtime-optimized memory path and preserves one scalar
// behavior model for these small, infrequently parsed coefficient tables.
scalingList.matrices[referenceIndex].CopyTo(scalingList.matrices[destinationIndex], 0);
scalingList.dcCoefficients[destinationIndex] = scalingList.dcCoefficients[referenceIndex];
}
continue;
}
int nextCoefficient = 8;
if (sizeId > 1)
{
nextCoefficient = (int)(((long)reader.ReadSignedExpGolomb() + 8) & 255);
scalingList.dcCoefficients[destinationIndex] = (byte)(nextCoefficient & 255);
}
byte[] scan = sizeId == 0 ? DiagonalScan4x4 : DiagonalScan8x8;
byte[] matrix = scalingList.matrices[destinationIndex];
for (int coefficient = 0; coefficient < matrix.Length; coefficient++)
{
nextCoefficient = (int)(((long)nextCoefficient + reader.ReadSignedExpGolomb()) & 255);
matrix[scan[coefficient]] = (byte)nextCoefficient;
}
}
if (sizeId == 3)
{
// HEVC signals only luma matrices at 32x32. Chroma uses the corresponding 16x16 matrices.
for (int matrixId = 0; matrixId < MatrixCount; matrixId++)
{
if (matrixId is 0 or 3)
{
continue;
}
int destinationIndex = GetIndex(sizeId, matrixId);
int referenceIndex = GetIndex(sizeId - 1, matrixId);
scalingList.matrices[referenceIndex].CopyTo(scalingList.matrices[destinationIndex], 0);
scalingList.dcCoefficients[destinationIndex] = scalingList.dcCoefficients[referenceIndex];
}
}
}
return scalingList;
}
/// <summary>
/// Gets a decoded scaling matrix.
/// </summary>
/// <param name="sizeId">The transform-size category from zero for 4x4 through three for 32x32.</param>
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
/// <returns>The 16 or 64 decoded scaling coefficients in raster order.</returns>
public ReadOnlySpan<byte> GetMatrix(int sizeId, int matrixId)
{
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId));
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId));
return this.matrices[GetIndex(sizeId, matrixId)];
}
/// <summary>
/// Gets the DC scaling coefficient for a large transform matrix.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
/// <returns>The decoded DC coefficient.</returns>
public byte GetDcCoefficient(int sizeId, int matrixId)
{
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId));
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId));
return this.dcCoefficients[GetIndex(sizeId, matrixId)];
}
/// <summary>
/// Gets the flattened storage index for a size and matrix identifier.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The flattened storage index.</returns>
private static int GetIndex(int sizeId, int matrixId) => (sizeId * MatrixCount) + matrixId;
/// <summary>
/// Creates the HEVC up-right diagonal scan for a square coefficient block.
/// </summary>
/// <param name="size">The coefficient block width and height.</param>
/// <returns>The raster indices in coded order.</returns>
private static byte[] CreateDiagonalScan(int size)
{
byte[] scan = new byte[size * size];
int row = 0;
int column = 0;
for (int position = 0; position < scan.Length; position++)
{
scan[position] = (byte)((row * size) + column);
if (column == size - 1 || row == 0)
{
row += column + 1;
column = 0;
if (row >= size)
{
column += row - (size - 1);
row = size - 1;
}
}
else
{
column++;
row--;
}
}
return scan;
}
}

464
src/ImageSharp/Formats/Heif/Hevc/HevcSequenceParameterSet.cs

@ -0,0 +1,464 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Contains the HEVC sequence fields required to reconstruct one independently coded still image.
/// </summary>
internal sealed class HevcSequenceParameterSet
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcSequenceParameterSet"/> class.
/// </summary>
/// <param name="nalUnit">The decoded sequence-parameter-set NAL unit.</param>
/// <exception cref="InvalidImageContentException">The sequence parameter set is malformed or outside the still-image profile.</exception>
public HevcSequenceParameterSet(HevcNalUnit nalUnit)
{
const byte sequenceParameterSetNalUnitType = 33;
if (nalUnit.Header.NalUnitType != sequenceParameterSetNalUnitType
|| nalUnit.Header.LayerId != 0
|| nalUnit.Header.TemporalId != 0)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid NAL-unit header.");
}
HevcBitReader reader = new(nalUnit.Rbsp.Span);
this.VideoParameterSetId = (byte)reader.ReadBits(4);
int maxSubLayersMinusOne = (int)reader.ReadBits(3);
if (maxSubLayersMinusOne > 6)
{
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many temporal sublayers.");
}
this.MaxSubLayers = maxSubLayersMinusOne + 1;
this.TemporalIdNestingFlag = reader.ReadFlag();
if (maxSubLayersMinusOne == 0 && !this.TemporalIdNestingFlag)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid temporal nesting.");
}
this.ProfileTierLevel = new HevcProfileTierLevel(ref reader, maxSubLayersMinusOne);
uint sequenceParameterSetId = reader.ReadUnsignedExpGolomb();
if (sequenceParameterSetId > 15)
{
throw new InvalidImageContentException("The HEVC sequence parameter set identifier is invalid.");
}
this.Id = (byte)sequenceParameterSetId;
uint chromaFormat = reader.ReadUnsignedExpGolomb();
if (chromaFormat > 3)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid chroma format.");
}
this.ChromaFormat = (byte)chromaFormat;
this.SeparateColorPlaneFlag = this.ChromaFormat == 3 && reader.ReadFlag();
uint width = reader.ReadUnsignedExpGolomb();
uint height = reader.ReadUnsignedExpGolomb();
if (width is 0 or > int.MaxValue || height is 0 or > int.MaxValue)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid coded dimensions.");
}
this.Width = (int)width;
this.Height = (int)height;
if (reader.ReadFlag())
{
int cropUnitWidth = HevcParameterSetSyntax.GetCropUnitWidth(this.ChromaFormat, this.SeparateColorPlaneFlag);
int cropUnitHeight = HevcParameterSetSyntax.GetCropUnitHeight(this.ChromaFormat, this.SeparateColorPlaneFlag);
this.ConformanceWindowLeftOffset = ReadScaledOffset(ref reader, cropUnitWidth);
this.ConformanceWindowRightOffset = ReadScaledOffset(ref reader, cropUnitWidth);
this.ConformanceWindowTopOffset = ReadScaledOffset(ref reader, cropUnitHeight);
this.ConformanceWindowBottomOffset = ReadScaledOffset(ref reader, cropUnitHeight);
}
if ((long)this.ConformanceWindowLeftOffset + this.ConformanceWindowRightOffset >= this.Width
|| (long)this.ConformanceWindowTopOffset + this.ConformanceWindowBottomOffset >= this.Height)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has an invalid conformance window.");
}
this.DisplayWidth = this.Width - this.ConformanceWindowLeftOffset - this.ConformanceWindowRightOffset;
this.DisplayHeight = this.Height - this.ConformanceWindowTopOffset - this.ConformanceWindowBottomOffset;
this.BitDepthLuma = ReadBitDepth(ref reader);
this.BitDepthChroma = ReadBitDepth(ref reader);
uint log2MaxPictureOrderCountLsbMinusFour = reader.ReadUnsignedExpGolomb();
if (log2MaxPictureOrderCountLsbMinusFour > 12)
{
throw new InvalidImageContentException("The HEVC picture-order-count width is invalid.");
}
this.PictureOrderCountLsbBits = (int)log2MaxPictureOrderCountLsbMinusFour + 4;
bool subLayerOrderingInfoPresent = reader.ReadFlag();
int firstOrderingSubLayer = subLayerOrderingInfoPresent ? 0 : maxSubLayersMinusOne;
for (int subLayer = firstOrderingSubLayer; subLayer <= maxSubLayersMinusOne; subLayer++)
{
uint maxDecodedPictureBufferingMinusOne = reader.ReadUnsignedExpGolomb();
uint maxNumReorderPictures = reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
if (maxNumReorderPictures > maxDecodedPictureBufferingMinusOne)
{
throw new InvalidImageContentException("The HEVC sequence parameter set has invalid sublayer ordering limits.");
}
}
uint minCodingBlockLog2MinusThree = reader.ReadUnsignedExpGolomb();
if (minCodingBlockLog2MinusThree > 3)
{
throw new InvalidImageContentException("The HEVC minimum coding-block size is invalid.");
}
this.MinCodingBlockLog2 = (int)minCodingBlockLog2MinusThree + 3;
uint codingBlockSizeDifference = reader.ReadUnsignedExpGolomb();
if (codingBlockSizeDifference > 6 - this.MinCodingBlockLog2)
{
throw new InvalidImageContentException("The HEVC coding-tree-block size is invalid.");
}
this.CodingTreeBlockLog2 = this.MinCodingBlockLog2 + (int)codingBlockSizeDifference;
uint minTransformBlockLog2MinusTwo = reader.ReadUnsignedExpGolomb();
if (minTransformBlockLog2MinusTwo > this.MinCodingBlockLog2 - 3)
{
throw new InvalidImageContentException("The HEVC minimum transform-block size is invalid.");
}
this.MinTransformBlockLog2 = (int)minTransformBlockLog2MinusTwo + 2;
uint transformBlockSizeDifference = reader.ReadUnsignedExpGolomb();
int maximumTransformBlockLog2 = Math.Min(5, this.CodingTreeBlockLog2);
if (transformBlockSizeDifference > maximumTransformBlockLog2 - this.MinTransformBlockLog2)
{
throw new InvalidImageContentException("The HEVC maximum transform-block size is invalid.");
}
this.MaxTransformBlockLog2 = this.MinTransformBlockLog2 + (int)transformBlockSizeDifference;
uint maxTransformHierarchyDepthInter = reader.ReadUnsignedExpGolomb();
uint maxTransformHierarchyDepthIntra = reader.ReadUnsignedExpGolomb();
uint maxHierarchyDepth = (uint)(this.CodingTreeBlockLog2 - this.MinTransformBlockLog2);
if (maxTransformHierarchyDepthInter > maxHierarchyDepth || maxTransformHierarchyDepthIntra > maxHierarchyDepth)
{
throw new InvalidImageContentException("The HEVC transform hierarchy depth is invalid.");
}
this.MaxTransformHierarchyDepthInter = (int)maxTransformHierarchyDepthInter + 1;
this.MaxTransformHierarchyDepthIntra = (int)maxTransformHierarchyDepthIntra + 1;
this.ScalingListEnabled = reader.ReadFlag();
this.ScalingList = new HevcScalingList();
if (this.ScalingListEnabled && reader.ReadFlag())
{
this.ScalingList = HevcScalingList.Parse(ref reader);
}
this.AsymmetricMotionPartitionsEnabled = reader.ReadFlag();
this.SampleAdaptiveOffsetEnabled = reader.ReadFlag();
this.PcmEnabled = reader.ReadFlag();
if (this.PcmEnabled)
{
this.PcmBitDepthLuma = (int)reader.ReadBits(4) + 1;
this.PcmBitDepthChroma = (int)reader.ReadBits(4) + 1;
if (this.PcmBitDepthLuma > this.BitDepthLuma || this.PcmBitDepthChroma > this.BitDepthChroma)
{
throw new InvalidImageContentException("The HEVC PCM bit depth exceeds the coded sample precision.");
}
uint minPcmCodingBlockLog2MinusThree = reader.ReadUnsignedExpGolomb();
this.MinPcmCodingBlockLog2 = (int)minPcmCodingBlockLog2MinusThree + 3;
int maximumPcmCodingBlockLog2 = Math.Min(this.CodingTreeBlockLog2, 5);
if (this.MinPcmCodingBlockLog2 < Math.Min(this.MinCodingBlockLog2, 5)
|| this.MinPcmCodingBlockLog2 > maximumPcmCodingBlockLog2)
{
throw new InvalidImageContentException("The HEVC minimum PCM coding-block size is invalid.");
}
uint pcmCodingBlockSizeDifference = reader.ReadUnsignedExpGolomb();
if (pcmCodingBlockSizeDifference > maximumPcmCodingBlockLog2 - this.MinPcmCodingBlockLog2)
{
throw new InvalidImageContentException("The HEVC maximum PCM coding-block size is invalid.");
}
this.MaxPcmCodingBlockLog2 = this.MinPcmCodingBlockLog2 + (int)pcmCodingBlockSizeDifference;
this.PcmLoopFilterDisabled = reader.ReadFlag();
}
uint shortTermReferencePictureSetCount = reader.ReadUnsignedExpGolomb();
if (shortTermReferencePictureSetCount > 64)
{
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many short-term reference-picture sets.");
}
List<HevcShortTermReferencePictureSet> shortTermReferencePictureSets = new((int)shortTermReferencePictureSetCount);
for (int referenceSet = 0; referenceSet < shortTermReferencePictureSetCount; referenceSet++)
{
shortTermReferencePictureSets.Add(
HevcShortTermReferencePictureSet.Parse(ref reader, shortTermReferencePictureSets, referenceSet));
}
this.ShortTermReferencePictureSets = shortTermReferencePictureSets;
if (reader.ReadFlag())
{
uint longTermReferencePictureCount = reader.ReadUnsignedExpGolomb();
if (longTermReferencePictureCount > 32)
{
throw new InvalidImageContentException("The HEVC sequence parameter set declares too many long-term reference pictures.");
}
uint[] pictureOrderCounts = new uint[longTermReferencePictureCount];
bool[] usedByCurrentPicture = new bool[longTermReferencePictureCount];
for (int reference = 0; reference < pictureOrderCounts.Length; reference++)
{
pictureOrderCounts[reference] = reader.ReadBits(this.PictureOrderCountLsbBits);
usedByCurrentPicture[reference] = reader.ReadFlag();
}
this.LongTermReferencePictureOrderCounts = pictureOrderCounts;
this.LongTermReferencePicturesUsedByCurrent = usedByCurrentPicture;
}
else
{
this.LongTermReferencePictureOrderCounts = Array.Empty<uint>();
this.LongTermReferencePicturesUsedByCurrent = Array.Empty<bool>();
}
this.TemporalMotionVectorPredictionEnabled = reader.ReadFlag();
this.StrongIntraSmoothingEnabled = reader.ReadFlag();
if (reader.ReadFlag())
{
this.VideoUsabilityInformation = new HevcVideoUsabilityInformation(
ref reader,
this.ChromaFormat,
this.SeparateColorPlaneFlag,
maxSubLayersMinusOne);
}
if (reader.ReadFlag())
{
Span<bool> extensionFlags = stackalloc bool[8];
for (int extensionFlag = 0; extensionFlag < extensionFlags.Length; extensionFlag++)
{
extensionFlags[extensionFlag] = reader.ReadFlag();
}
if (extensionFlags[1])
{
throw new InvalidImageContentException("Layered HEVC sequence extensions are not supported for still-image items.");
}
if (extensionFlags[0])
{
this.TransformSkipRotationEnabled = reader.ReadFlag();
this.TransformSkipContextEnabled = reader.ReadFlag();
this.ImplicitResidualDpcmEnabled = reader.ReadFlag();
this.ExplicitResidualDpcmEnabled = reader.ReadFlag();
this.ExtendedPrecisionProcessingEnabled = reader.ReadFlag();
this.IntraSmoothingDisabled = reader.ReadFlag();
this.HighPrecisionOffsetsEnabled = reader.ReadFlag();
this.PersistentRiceAdaptationEnabled = reader.ReadFlag();
this.CabacBypassAlignmentEnabled = reader.ReadFlag();
}
bool unknownExtensionPresent = false;
for (int extensionFlag = 2; extensionFlag < extensionFlags.Length; extensionFlag++)
{
unknownExtensionPresent |= extensionFlags[extensionFlag];
}
if (unknownExtensionPresent)
{
while (reader.HasMoreRbspData())
{
reader.ReadFlag();
}
}
}
reader.ReadRbspTrailingBits();
}
/// <summary>Gets the referenced video-parameter-set identifier.</summary>
public byte VideoParameterSetId { get; }
/// <summary>Gets the sequence-parameter-set identifier.</summary>
public byte Id { get; }
/// <summary>Gets the declared number of temporal sublayers.</summary>
public int MaxSubLayers { get; }
/// <summary>Gets a value indicating whether temporal identifiers are nested.</summary>
public bool TemporalIdNestingFlag { get; }
/// <summary>Gets the general profile, tier, constraint, and level description.</summary>
public HevcProfileTierLevel ProfileTierLevel { get; }
/// <summary>Gets the coded chroma format, from monochrome through YUV 4:4:4.</summary>
public byte ChromaFormat { get; }
/// <summary>Gets a value indicating whether 4:4:4 components are coded as separate color planes.</summary>
public bool SeparateColorPlaneFlag { get; }
/// <summary>Gets the coded luma width before conformance cropping.</summary>
public int Width { get; }
/// <summary>Gets the coded luma height before conformance cropping.</summary>
public int Height { get; }
/// <summary>Gets the displayed width after conformance cropping.</summary>
public int DisplayWidth { get; }
/// <summary>Gets the displayed height after conformance cropping.</summary>
public int DisplayHeight { get; }
/// <summary>Gets the conformance-window left offset in luma samples.</summary>
public int ConformanceWindowLeftOffset { get; }
/// <summary>Gets the conformance-window right offset in luma samples.</summary>
public int ConformanceWindowRightOffset { get; }
/// <summary>Gets the conformance-window top offset in luma samples.</summary>
public int ConformanceWindowTopOffset { get; }
/// <summary>Gets the conformance-window bottom offset in luma samples.</summary>
public int ConformanceWindowBottomOffset { get; }
/// <summary>Gets the luma sample precision in bits.</summary>
public int BitDepthLuma { get; }
/// <summary>Gets the chroma sample precision in bits.</summary>
public int BitDepthChroma { get; }
/// <summary>Gets the coded picture-order-count least-significant-bit width.</summary>
public int PictureOrderCountLsbBits { get; }
/// <summary>Gets the base-two logarithm of the minimum luma coding-block size.</summary>
public int MinCodingBlockLog2 { get; }
/// <summary>Gets the base-two logarithm of the coding-tree-block size.</summary>
public int CodingTreeBlockLog2 { get; }
/// <summary>Gets the base-two logarithm of the minimum luma transform-block size.</summary>
public int MinTransformBlockLog2 { get; }
/// <summary>Gets the base-two logarithm of the maximum luma transform-block size.</summary>
public int MaxTransformBlockLog2 { get; }
/// <summary>Gets the maximum inter-predicted transform hierarchy depth.</summary>
public int MaxTransformHierarchyDepthInter { get; }
/// <summary>Gets the maximum intra-predicted transform hierarchy depth.</summary>
public int MaxTransformHierarchyDepthIntra { get; }
/// <summary>Gets a value indicating whether scaling lists affect inverse quantization.</summary>
public bool ScalingListEnabled { get; }
/// <summary>Gets the effective quantization scaling matrices.</summary>
public HevcScalingList ScalingList { get; }
/// <summary>Gets a value indicating whether asymmetric motion partitions are enabled.</summary>
public bool AsymmetricMotionPartitionsEnabled { get; }
/// <summary>Gets a value indicating whether sample-adaptive offset filtering is enabled.</summary>
public bool SampleAdaptiveOffsetEnabled { get; }
/// <summary>Gets a value indicating whether pulse-code-modulated coding blocks are enabled.</summary>
public bool PcmEnabled { get; }
/// <summary>Gets the PCM luma sample precision in bits.</summary>
public int PcmBitDepthLuma { get; }
/// <summary>Gets the PCM chroma sample precision in bits.</summary>
public int PcmBitDepthChroma { get; }
/// <summary>Gets the base-two logarithm of the minimum PCM coding-block size.</summary>
public int MinPcmCodingBlockLog2 { get; }
/// <summary>Gets the base-two logarithm of the maximum PCM coding-block size.</summary>
public int MaxPcmCodingBlockLog2 { get; }
/// <summary>Gets a value indicating whether in-loop filtering is disabled for PCM blocks.</summary>
public bool PcmLoopFilterDisabled { get; }
/// <summary>Gets the SPS short-term reference-picture sets.</summary>
public IReadOnlyList<HevcShortTermReferencePictureSet> ShortTermReferencePictureSets { get; }
/// <summary>Gets the long-term reference picture-order-count values.</summary>
public IReadOnlyList<uint> LongTermReferencePictureOrderCounts { get; }
/// <summary>Gets the long-term reference-picture current-usage flags.</summary>
public IReadOnlyList<bool> LongTermReferencePicturesUsedByCurrent { get; }
/// <summary>Gets a value indicating whether temporal motion-vector prediction is enabled.</summary>
public bool TemporalMotionVectorPredictionEnabled { get; }
/// <summary>Gets a value indicating whether strong intra smoothing is enabled.</summary>
public bool StrongIntraSmoothingEnabled { get; }
/// <summary>Gets the optional still-image VUI presentation description.</summary>
public HevcVideoUsabilityInformation? VideoUsabilityInformation { get; }
/// <summary>Gets a value indicating whether transform-skip coefficient rotation is enabled.</summary>
public bool TransformSkipRotationEnabled { get; }
/// <summary>Gets a value indicating whether transform-skip-specific entropy contexts are enabled.</summary>
public bool TransformSkipContextEnabled { get; }
/// <summary>Gets a value indicating whether implicit residual DPCM is enabled.</summary>
public bool ImplicitResidualDpcmEnabled { get; }
/// <summary>Gets a value indicating whether explicit residual DPCM is enabled.</summary>
public bool ExplicitResidualDpcmEnabled { get; }
/// <summary>Gets a value indicating whether extended-precision processing is enabled.</summary>
public bool ExtendedPrecisionProcessingEnabled { get; }
/// <summary>Gets a value indicating whether intra smoothing is disabled.</summary>
public bool IntraSmoothingDisabled { get; }
/// <summary>Gets a value indicating whether high-precision prediction offsets are enabled.</summary>
public bool HighPrecisionOffsetsEnabled { get; }
/// <summary>Gets a value indicating whether persistent Rice adaptation is enabled.</summary>
public bool PersistentRiceAdaptationEnabled { get; }
/// <summary>Gets a value indicating whether CABAC bypass alignment is enabled.</summary>
public bool CabacBypassAlignmentEnabled { get; }
/// <summary>
/// Reads a conformance-window offset and converts it to luma-sample units.
/// </summary>
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
/// <param name="unit">The chroma-dependent luma-sample unit.</param>
/// <returns>The scaled offset.</returns>
/// <exception cref="InvalidImageContentException">The scaled offset exceeds the supported image dimension range.</exception>
private static int ReadScaledOffset(ref HevcBitReader reader, int unit)
{
uint offset = reader.ReadUnsignedExpGolomb();
if (offset > int.MaxValue / unit)
{
throw new InvalidImageContentException("The HEVC conformance-window offset is too large.");
}
return (int)offset * unit;
}
/// <summary>
/// Reads and validates a coded HEVC sample precision.
/// </summary>
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
/// <returns>The sample precision in bits.</returns>
/// <exception cref="InvalidImageContentException">The declared precision exceeds 16 bits.</exception>
private static int ReadBitDepth(ref HevcBitReader reader)
{
uint bitDepthMinusEight = reader.ReadUnsignedExpGolomb();
if (bitDepthMinusEight > 8)
{
throw new InvalidImageContentException("The HEVC sample bit depth is invalid.");
}
return (int)bitDepthMinusEight + 8;
}
}

178
src/ImageSharp/Formats/Heif/Hevc/HevcShortTermReferencePictureSet.cs

@ -0,0 +1,178 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Contains the bounded picture-order differences declared by one HEVC short-term reference-picture set.
/// </summary>
internal sealed class HevcShortTermReferencePictureSet
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcShortTermReferencePictureSet"/> class.
/// </summary>
/// <param name="deltaPictureOrders">The signed picture-order differences in HEVC reference order.</param>
/// <param name="usedByCurrentPicture">The corresponding current-picture usage flags.</param>
private HevcShortTermReferencePictureSet(int[] deltaPictureOrders, bool[] usedByCurrentPicture)
{
this.DeltaPictureOrders = deltaPictureOrders;
this.UsedByCurrentPicture = usedByCurrentPicture;
}
/// <summary>
/// Gets the signed picture-order differences in HEVC reference order.
/// </summary>
public IReadOnlyList<int> DeltaPictureOrders { get; }
/// <summary>
/// Gets the flags indicating which reference pictures are used by the current picture.
/// </summary>
public IReadOnlyList<bool> UsedByCurrentPicture { get; }
/// <summary>
/// Gets the number of pictures declared by the reference-picture set.
/// </summary>
public int Count => this.DeltaPictureOrders.Count;
/// <summary>
/// Reads one SPS short-term reference-picture set.
/// </summary>
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
/// <param name="previousSets">The previously decoded sets available for inter-set prediction.</param>
/// <param name="index">The zero-based index of the set being decoded.</param>
/// <returns>The decoded reference-picture set.</returns>
/// <exception cref="InvalidImageContentException">The set exceeds the HEVC decoded-picture-buffer bound.</exception>
public static HevcShortTermReferencePictureSet Parse(
ref HevcBitReader reader,
IReadOnlyList<HevcShortTermReferencePictureSet> previousSets,
int index)
{
Span<int> deltaPictureOrders = stackalloc int[16];
Span<bool> usedByCurrentPicture = stackalloc bool[16];
int pictureCount = 0;
bool interSetPrediction = index > 0 && reader.ReadFlag();
if (interSetPrediction)
{
HevcShortTermReferencePictureSet referenceSet = previousSets[index - 1];
bool deltaPictureOrderSign = reader.ReadFlag();
uint absoluteDeltaPictureOrderMinusOne = reader.ReadUnsignedExpGolomb();
if (absoluteDeltaPictureOrderMinusOne >= int.MaxValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
int deltaReferencePictureSet = (deltaPictureOrderSign ? -1 : 1)
* ((int)absoluteDeltaPictureOrderMinusOne + 1);
for (int referenceIndex = 0; referenceIndex <= referenceSet.Count; referenceIndex++)
{
bool used = reader.ReadFlag();
bool useDelta = used || reader.ReadFlag();
if (!useDelta)
{
continue;
}
if (pictureCount == deltaPictureOrders.Length)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture set is too large.");
}
int referenceDelta = referenceIndex < referenceSet.Count
? referenceSet.DeltaPictureOrders[referenceIndex]
: 0;
long deltaPictureOrder = (long)deltaReferencePictureSet + referenceDelta;
if (deltaPictureOrder is < int.MinValue or > int.MaxValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
deltaPictureOrders[pictureCount] = (int)deltaPictureOrder;
usedByCurrentPicture[pictureCount] = used;
pictureCount++;
}
// HEVC orders negative differences nearest-first, followed by positive differences nearest-first.
for (int outer = 1; outer < pictureCount; outer++)
{
int delta = deltaPictureOrders[outer];
bool used = usedByCurrentPicture[outer];
int inner = outer - 1;
while (inner >= 0 && delta < deltaPictureOrders[inner])
{
deltaPictureOrders[inner + 1] = deltaPictureOrders[inner];
usedByCurrentPicture[inner + 1] = usedByCurrentPicture[inner];
inner--;
}
deltaPictureOrders[inner + 1] = delta;
usedByCurrentPicture[inner + 1] = used;
}
int negativeCount = 0;
while (negativeCount < pictureCount && deltaPictureOrders[negativeCount] < 0)
{
negativeCount++;
}
deltaPictureOrders[..negativeCount].Reverse();
usedByCurrentPicture[..negativeCount].Reverse();
}
else
{
uint negativePictureCount = reader.ReadUnsignedExpGolomb();
uint positivePictureCount = reader.ReadUnsignedExpGolomb();
if (negativePictureCount > 16 || positivePictureCount > 16 - negativePictureCount)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture set is too large.");
}
int previousDelta = 0;
for (uint negativeIndex = 0; negativeIndex < negativePictureCount; negativeIndex++)
{
uint deltaMinusOne = reader.ReadUnsignedExpGolomb();
if (deltaMinusOne >= int.MaxValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
long deltaPictureOrder = (long)previousDelta - deltaMinusOne - 1;
if (deltaPictureOrder < int.MinValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
previousDelta = (int)deltaPictureOrder;
deltaPictureOrders[pictureCount] = previousDelta;
usedByCurrentPicture[pictureCount] = reader.ReadFlag();
pictureCount++;
}
previousDelta = 0;
for (uint positiveIndex = 0; positiveIndex < positivePictureCount; positiveIndex++)
{
uint deltaMinusOne = reader.ReadUnsignedExpGolomb();
if (deltaMinusOne >= int.MaxValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
long deltaPictureOrder = (long)previousDelta + deltaMinusOne + 1;
if (deltaPictureOrder > int.MaxValue)
{
throw new InvalidImageContentException("The HEVC short-term reference-picture delta is too large.");
}
previousDelta = (int)deltaPictureOrder;
deltaPictureOrders[pictureCount] = previousDelta;
usedByCurrentPicture[pictureCount] = reader.ReadFlag();
pictureCount++;
}
}
return new HevcShortTermReferencePictureSet(
deltaPictureOrders[..pictureCount].ToArray(),
usedByCurrentPicture[..pictureCount].ToArray());
}
}

97
src/ImageSharp/Formats/Heif/Hevc/HevcVideoParameterSet.cs

@ -113,7 +113,7 @@ internal sealed class HevcVideoParameterSet
}
bool commonInformationPresent = hrdIndex == 0 || reader.ReadFlag();
SkipHrdParameters(
HevcParameterSetSyntax.SkipHrdParameters(
ref reader,
commonInformationPresent,
maxSubLayersMinusOne,
@ -153,99 +153,4 @@ internal sealed class HevcVideoParameterSet
/// Gets the general profile, tier, constraint, and level description.
/// </summary>
public HevcProfileTierLevel ProfileTierLevel { get; }
/// <summary>
/// Consumes hypothetical-reference-decoder syntax without adding playback state to the still-image model.
/// </summary>
/// <param name="reader">The video-parameter-set raw byte sequence payload reader.</param>
/// <param name="commonInformationPresent">Whether common HRD flags are coded for this parameter set.</param>
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
/// <param name="nalHrdParametersPresent">The effective NAL HRD presence flag.</param>
/// <param name="vclHrdParametersPresent">The effective VCL HRD presence flag.</param>
/// <param name="subPictureHrdParametersPresent">The effective sub-picture HRD presence flag.</param>
/// <exception cref="InvalidImageContentException">The HRD syntax is truncated or exceeds its registered bounds.</exception>
private static void SkipHrdParameters(
ref HevcBitReader reader,
bool commonInformationPresent,
int maxSubLayersMinusOne,
ref bool nalHrdParametersPresent,
ref bool vclHrdParametersPresent,
ref bool subPictureHrdParametersPresent)
{
if (commonInformationPresent)
{
nalHrdParametersPresent = reader.ReadFlag();
vclHrdParametersPresent = reader.ReadFlag();
subPictureHrdParametersPresent = false;
if (nalHrdParametersPresent || vclHrdParametersPresent)
{
subPictureHrdParametersPresent = reader.ReadFlag();
if (subPictureHrdParametersPresent)
{
reader.ReadBits(8);
reader.ReadBits(5);
reader.ReadFlag();
reader.ReadBits(5);
}
reader.ReadBits(4);
reader.ReadBits(4);
if (subPictureHrdParametersPresent)
{
reader.ReadBits(4);
}
reader.ReadBits(5);
reader.ReadBits(5);
reader.ReadBits(5);
}
}
for (int subLayer = 0; subLayer <= maxSubLayersMinusOne; subLayer++)
{
bool fixedPictureRateGeneral = reader.ReadFlag();
bool fixedPictureRateWithinCvs = fixedPictureRateGeneral || reader.ReadFlag();
bool lowDelayHrd = false;
if (fixedPictureRateWithinCvs)
{
reader.ReadUnsignedExpGolomb();
}
else
{
lowDelayHrd = reader.ReadFlag();
}
uint cpbCountMinusOne = 0;
if (!lowDelayHrd)
{
cpbCountMinusOne = reader.ReadUnsignedExpGolomb();
if (cpbCountMinusOne > 31)
{
throw new InvalidImageContentException("The HEVC HRD syntax declares too many coded-picture buffers.");
}
}
for (int hrdKind = 0; hrdKind < 2; hrdKind++)
{
bool parametersPresent = hrdKind == 0 ? nalHrdParametersPresent : vclHrdParametersPresent;
if (!parametersPresent)
{
continue;
}
for (uint cpbIndex = 0; cpbIndex <= cpbCountMinusOne; cpbIndex++)
{
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
if (subPictureHrdParametersPresent)
{
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
}
reader.ReadFlag();
}
}
}
}
}

247
src/ImageSharp/Formats/Heif/Hevc/HevcVideoUsabilityInformation.cs

@ -0,0 +1,247 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Contains the still-image presentation fields declared by HEVC video-usability information.
/// </summary>
internal sealed class HevcVideoUsabilityInformation
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcVideoUsabilityInformation"/> class.
/// </summary>
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
/// <param name="chromaFormat">The sequence chroma-format identifier.</param>
/// <param name="separateColorPlane">Whether 4:4:4 components are coded as separate planes.</param>
/// <param name="maxSubLayersMinusOne">The highest declared temporal sublayer index.</param>
/// <exception cref="InvalidImageContentException">The VUI syntax is invalid for a still-image item.</exception>
public HevcVideoUsabilityInformation(
ref HevcBitReader reader,
byte chromaFormat,
bool separateColorPlane,
int maxSubLayersMinusOne)
{
this.AspectRatioInfoPresent = reader.ReadFlag();
if (this.AspectRatioInfoPresent)
{
this.AspectRatioIdc = (byte)reader.ReadBits(8);
if (this.AspectRatioIdc == byte.MaxValue)
{
this.SarWidth = (ushort)reader.ReadBits(16);
this.SarHeight = (ushort)reader.ReadBits(16);
if (this.SarWidth == 0 || this.SarHeight == 0)
{
throw new InvalidImageContentException("The HEVC VUI declares an invalid extended sample aspect ratio.");
}
}
else if (this.AspectRatioIdc > 16)
{
throw new InvalidImageContentException("The HEVC VUI declares a reserved sample aspect ratio.");
}
}
if (reader.ReadFlag())
{
reader.ReadFlag();
}
this.VideoSignalTypePresent = reader.ReadFlag();
if (this.VideoSignalTypePresent)
{
reader.ReadBits(3);
this.FullRange = reader.ReadFlag();
this.ColorDescriptionPresent = reader.ReadFlag();
if (this.ColorDescriptionPresent)
{
this.ColorPrimaries = (byte)reader.ReadBits(8);
this.TransferCharacteristics = (byte)reader.ReadBits(8);
this.MatrixCoefficients = (byte)reader.ReadBits(8);
}
}
this.ChromaLocationInfoPresent = reader.ReadFlag();
if (this.ChromaLocationInfoPresent)
{
uint topFieldLocation = reader.ReadUnsignedExpGolomb();
uint bottomFieldLocation = reader.ReadUnsignedExpGolomb();
if (topFieldLocation > 5 || bottomFieldLocation > 5)
{
throw new InvalidImageContentException("The HEVC VUI declares an invalid chroma sample location.");
}
this.ChromaSampleLocationTopField = (byte)topFieldLocation;
this.ChromaSampleLocationBottomField = (byte)bottomFieldLocation;
}
reader.ReadFlag();
if (reader.ReadFlag())
{
// A field sequence requires paired-field presentation state and is not a single HEIF image item.
throw new InvalidImageContentException("Interlaced HEVC field sequences are not supported as still-image items.");
}
reader.ReadFlag();
this.DefaultDisplayWindowPresent = reader.ReadFlag();
if (this.DefaultDisplayWindowPresent)
{
int cropUnitWidth = HevcParameterSetSyntax.GetCropUnitWidth(chromaFormat, separateColorPlane);
int cropUnitHeight = HevcParameterSetSyntax.GetCropUnitHeight(chromaFormat, separateColorPlane);
this.DefaultDisplayWindowLeftOffset = ReadScaledOffset(ref reader, cropUnitWidth);
this.DefaultDisplayWindowRightOffset = ReadScaledOffset(ref reader, cropUnitWidth);
this.DefaultDisplayWindowTopOffset = ReadScaledOffset(ref reader, cropUnitHeight);
this.DefaultDisplayWindowBottomOffset = ReadScaledOffset(ref reader, cropUnitHeight);
}
if (reader.ReadFlag())
{
// VUI timing and HRD fields affect scheduling, not the reconstructed still-image samples.
reader.ReadBits(32);
reader.ReadBits(32);
if (reader.ReadFlag())
{
reader.ReadUnsignedExpGolomb();
}
if (reader.ReadFlag())
{
bool nalHrdParametersPresent = false;
bool vclHrdParametersPresent = false;
bool subPictureHrdParametersPresent = false;
HevcParameterSetSyntax.SkipHrdParameters(
ref reader,
true,
maxSubLayersMinusOne,
ref nalHrdParametersPresent,
ref vclHrdParametersPresent,
ref subPictureHrdParametersPresent);
}
}
if (reader.ReadFlag())
{
reader.ReadFlag();
reader.ReadFlag();
reader.ReadFlag();
uint minimumSpatialSegmentation = reader.ReadUnsignedExpGolomb();
if (minimumSpatialSegmentation >= 4096)
{
throw new InvalidImageContentException("The HEVC VUI spatial-segmentation value is invalid.");
}
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
reader.ReadUnsignedExpGolomb();
}
}
/// <summary>
/// Gets a value indicating whether sample-aspect-ratio information is present.
/// </summary>
public bool AspectRatioInfoPresent { get; }
/// <summary>
/// Gets the registered sample-aspect-ratio identifier.
/// </summary>
public byte AspectRatioIdc { get; }
/// <summary>
/// Gets the explicit horizontal sample spacing when <see cref="AspectRatioIdc"/> is 255.
/// </summary>
public ushort SarWidth { get; }
/// <summary>
/// Gets the explicit vertical sample spacing when <see cref="AspectRatioIdc"/> is 255.
/// </summary>
public ushort SarHeight { get; }
/// <summary>
/// Gets a value indicating whether video-signal-type information is present.
/// </summary>
public bool VideoSignalTypePresent { get; }
/// <summary>
/// Gets a value indicating whether component samples use the full numeric range.
/// </summary>
public bool FullRange { get; }
/// <summary>
/// Gets a value indicating whether color-description fields are present.
/// </summary>
public bool ColorDescriptionPresent { get; }
/// <summary>
/// Gets the coded color-primary identifier.
/// </summary>
public byte ColorPrimaries { get; }
/// <summary>
/// Gets the coded transfer-characteristic identifier.
/// </summary>
public byte TransferCharacteristics { get; }
/// <summary>
/// Gets the coded matrix-coefficient identifier.
/// </summary>
public byte MatrixCoefficients { get; }
/// <summary>
/// Gets a value indicating whether chroma sample-location information is present.
/// </summary>
public bool ChromaLocationInfoPresent { get; }
/// <summary>
/// Gets the top-field chroma sample-location identifier.
/// </summary>
public byte ChromaSampleLocationTopField { get; }
/// <summary>
/// Gets the bottom-field chroma sample-location identifier.
/// </summary>
public byte ChromaSampleLocationBottomField { get; }
/// <summary>
/// Gets a value indicating whether a default display window is present.
/// </summary>
public bool DefaultDisplayWindowPresent { get; }
/// <summary>
/// Gets the default display-window left offset in luma samples.
/// </summary>
public int DefaultDisplayWindowLeftOffset { get; }
/// <summary>
/// Gets the default display-window right offset in luma samples.
/// </summary>
public int DefaultDisplayWindowRightOffset { get; }
/// <summary>
/// Gets the default display-window top offset in luma samples.
/// </summary>
public int DefaultDisplayWindowTopOffset { get; }
/// <summary>
/// Gets the default display-window bottom offset in luma samples.
/// </summary>
public int DefaultDisplayWindowBottomOffset { get; }
/// <summary>
/// Reads a conformance-window offset and converts it to luma-sample units.
/// </summary>
/// <param name="reader">The sequence-parameter-set raw byte sequence payload reader.</param>
/// <param name="unit">The chroma-dependent luma-sample unit.</param>
/// <returns>The scaled offset.</returns>
/// <exception cref="InvalidImageContentException">The scaled offset exceeds the supported image dimension range.</exception>
private static int ReadScaledOffset(ref HevcBitReader reader, int unit)
{
uint offset = reader.ReadUnsignedExpGolomb();
if (offset > int.MaxValue / unit)
{
throw new InvalidImageContentException("The HEVC VUI display-window offset is too large.");
}
return (int)offset * unit;
}
}
Loading…
Cancel
Save