Browse Source

Parse HEVC picture parameter sets

pull/2633/head
James Jackson-South 1 week ago
parent
commit
bcecc74a2f
  1. 1
      HEIF_IMPLEMENTATION_PLAN.md
  2. 22
      src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs
  3. 511
      src/ImageSharp/Formats/Heif/Hevc/HevcPictureParameterSet.cs

1
HEIF_IMPLEMENTATION_PLAN.md

@ -115,6 +115,7 @@ This snapshot pins or classifies the available references and failures; it does
| `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. |
| `HevcPictureParameterSet` and `HevcCodecConfiguration.PictureParameterSets` | HEVC sections 7.3.2.3 picture parameter sets, 7.3.4 scaling-list data, and Range Extensions PPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` function `parsePPS`, `source/Lib/TLibDecoder/TDecConformance.cpp` PPS range checks, and `source/Lib/TLibCommon/TComPicSym.cpp` tile derivation at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` PPS and tile paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Link each PPS to a configuration-local SPS and retain the CABAC, quantization, transform-skip, scaling-list, tile/wavefront, deblocking, SAO, slice-header, and Range Extensions controls required to reconstruct the independently coded picture. Derive the complete CTB tile grid and validate every SPS-dependent depth, size, precision, and offset bound during parsing. Reject multilayer extensions and add no access-unit, reference-picture lifetime, playback, track, timing, or sample-table model. |
| `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. |

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

@ -199,6 +199,23 @@ internal sealed class HevcCodecConfiguration
}
this.SequenceParameterSets = sequenceParameterSets;
List<HevcPictureParameterSet> pictureParameterSets = new();
foreach (HevcNalUnitArray nalUnitArray in this.nalUnitArrays)
{
const byte pictureParameterSetNalUnitType = 34;
if (nalUnitArray.NalUnitType != pictureParameterSetNalUnitType)
{
continue;
}
foreach (HevcNalUnit nalUnit in nalUnitArray.NalUnits)
{
pictureParameterSets.Add(new HevcPictureParameterSet(nalUnit, sequenceParameterSets));
}
}
this.PictureParameterSets = pictureParameterSets;
}
/// <summary>
@ -277,6 +294,11 @@ internal sealed class HevcCodecConfiguration
/// </summary>
public IReadOnlyList<HevcSequenceParameterSet> SequenceParameterSets { get; }
/// <summary>
/// Gets the validated picture parameter sets carried by the codec-configuration property.
/// </summary>
public IReadOnlyList<HevcPictureParameterSet> PictureParameterSets { get; }
/// <summary>
/// Validates the associated pixel-information property against the coded luma and chroma sample precisions.
/// </summary>

511
src/ImageSharp/Formats/Heif/Hevc/HevcPictureParameterSet.cs

@ -0,0 +1,511 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Contains the HEVC picture fields required to decode the independently coded picture in one still-image item.
/// </summary>
internal sealed class HevcPictureParameterSet
{
/// <summary>
/// Initializes a new instance of the <see cref="HevcPictureParameterSet"/> class.
/// </summary>
/// <param name="nalUnit">The decoded picture-parameter-set NAL unit.</param>
/// <param name="sequenceParameterSets">The sequence parameter sets available to the coded image item.</param>
/// <exception cref="InvalidImageContentException">
/// The picture parameter set is malformed, references an unavailable sequence parameter set, or declares
/// picture geometry outside that sequence parameter set.
/// </exception>
public HevcPictureParameterSet(
HevcNalUnit nalUnit,
IReadOnlyList<HevcSequenceParameterSet> sequenceParameterSets)
{
const byte pictureParameterSetNalUnitType = 34;
if (nalUnit.Header.NalUnitType != pictureParameterSetNalUnitType
|| nalUnit.Header.LayerId != 0
|| nalUnit.Header.TemporalId != 0)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid NAL-unit header.");
}
HevcBitReader reader = new(nalUnit.Rbsp.Span);
uint pictureParameterSetId = reader.ReadUnsignedExpGolomb();
uint sequenceParameterSetId = reader.ReadUnsignedExpGolomb();
if (pictureParameterSetId > 63 || sequenceParameterSetId > 15)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid identifier.");
}
this.Id = (byte)pictureParameterSetId;
this.SequenceParameterSetId = (byte)sequenceParameterSetId;
HevcSequenceParameterSet? sequenceParameterSet = null;
foreach (HevcSequenceParameterSet candidate in sequenceParameterSets)
{
if (candidate.Id == this.SequenceParameterSetId)
{
sequenceParameterSet = candidate;
break;
}
}
if (sequenceParameterSet is null)
{
throw new InvalidImageContentException("The HEVC picture parameter set references an unavailable sequence parameter set.");
}
this.SequenceParameterSet = sequenceParameterSet;
this.DependentSliceSegmentsEnabled = reader.ReadFlag();
this.OutputFlagPresent = reader.ReadFlag();
this.ExtraSliceHeaderBitCount = (int)reader.ReadBits(3);
this.SignDataHidingEnabled = reader.ReadFlag();
this.CabacInitializationPresent = reader.ReadFlag();
uint defaultReferenceIndexCountList0MinusOne = reader.ReadUnsignedExpGolomb();
uint defaultReferenceIndexCountList1MinusOne = reader.ReadUnsignedExpGolomb();
if (defaultReferenceIndexCountList0MinusOne > 14 || defaultReferenceIndexCountList1MinusOne > 14)
{
throw new InvalidImageContentException("The HEVC picture parameter set declares too many default reference indices.");
}
this.DefaultReferenceIndexCountList0 = (int)defaultReferenceIndexCountList0MinusOne + 1;
this.DefaultReferenceIndexCountList1 = (int)defaultReferenceIndexCountList1MinusOne + 1;
this.InitialQuantizationParameterMinus26 = reader.ReadSignedExpGolomb();
int minimumInitialQuantizationParameter = -26 - (6 * (sequenceParameterSet.BitDepthLuma - 8));
if (this.InitialQuantizationParameterMinus26 < minimumInitialQuantizationParameter
|| this.InitialQuantizationParameterMinus26 > 25)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid initial quantization parameter.");
}
this.ConstrainedIntraPredictionEnabled = reader.ReadFlag();
this.TransformSkipEnabled = reader.ReadFlag();
this.CodingUnitQuantizationParameterDeltaEnabled = reader.ReadFlag();
if (this.CodingUnitQuantizationParameterDeltaEnabled)
{
uint quantizationParameterDeltaDepth = reader.ReadUnsignedExpGolomb();
int maximumDepth = sequenceParameterSet.CodingTreeBlockLog2 - sequenceParameterSet.MinCodingBlockLog2;
if (quantizationParameterDeltaDepth > maximumDepth)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid quantization-parameter delta depth.");
}
this.QuantizationParameterDeltaDepth = (int)quantizationParameterDeltaDepth;
}
this.ChromaCbQuantizationParameterOffset = ReadQuantizationParameterOffset(ref reader);
this.ChromaCrQuantizationParameterOffset = ReadQuantizationParameterOffset(ref reader);
this.SliceChromaQuantizationParameterOffsetsPresent = reader.ReadFlag();
this.WeightedPredictionEnabled = reader.ReadFlag();
this.WeightedBiPredictionEnabled = reader.ReadFlag();
this.TransquantizationBypassEnabled = reader.ReadFlag();
this.TilesEnabled = reader.ReadFlag();
this.EntropyCodingSynchronizationEnabled = reader.ReadFlag();
int codingTreeBlockColumns = GetCodingTreeBlockCount(
sequenceParameterSet.Width,
sequenceParameterSet.CodingTreeBlockLog2);
int codingTreeBlockRows = GetCodingTreeBlockCount(
sequenceParameterSet.Height,
sequenceParameterSet.CodingTreeBlockLog2);
if (this.TilesEnabled)
{
uint tileColumnCountMinusOne = reader.ReadUnsignedExpGolomb();
uint tileRowCountMinusOne = reader.ReadUnsignedExpGolomb();
if (tileColumnCountMinusOne >= codingTreeBlockColumns
|| tileRowCountMinusOne >= codingTreeBlockRows
|| (tileColumnCountMinusOne == 0 && tileRowCountMinusOne == 0))
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid tile grid.");
}
int tileColumnCount = (int)tileColumnCountMinusOne + 1;
int tileRowCount = (int)tileRowCountMinusOne + 1;
this.UniformTileSpacing = reader.ReadFlag();
this.TileColumnWidths = ReadTileDimensions(
ref reader,
codingTreeBlockColumns,
tileColumnCount,
this.UniformTileSpacing);
this.TileRowHeights = ReadTileDimensions(
ref reader,
codingTreeBlockRows,
tileRowCount,
this.UniformTileSpacing);
this.LoopFilterAcrossTilesEnabled = reader.ReadFlag();
}
else
{
// A picture without tile syntax is one tile spanning the coded CTB grid. Materializing that inferred
// layout lets slice addressing use the same bounded arrays for tiled and untiled image items.
this.UniformTileSpacing = true;
this.TileColumnWidths = [codingTreeBlockColumns];
this.TileRowHeights = [codingTreeBlockRows];
this.LoopFilterAcrossTilesEnabled = true;
}
this.LoopFilterAcrossSlicesEnabled = reader.ReadFlag();
this.DeblockingFilterControlPresent = reader.ReadFlag();
if (this.DeblockingFilterControlPresent)
{
this.DeblockingFilterOverrideEnabled = reader.ReadFlag();
this.DeblockingFilterDisabled = reader.ReadFlag();
if (!this.DeblockingFilterDisabled)
{
this.DeblockingFilterBetaOffsetDiv2 = ReadDeblockingFilterOffset(ref reader);
this.DeblockingFilterTcOffsetDiv2 = ReadDeblockingFilterOffset(ref reader);
}
}
this.ScalingListDataPresent = reader.ReadFlag();
if (this.ScalingListDataPresent && !sequenceParameterSet.ScalingListEnabled)
{
throw new InvalidImageContentException("The HEVC picture parameter set declares scaling data disabled by its sequence parameter set.");
}
this.ScalingList = this.ScalingListDataPresent
? HevcScalingList.Parse(ref reader)
: sequenceParameterSet.ScalingList;
this.ReferenceListModificationPresent = reader.ReadFlag();
uint parallelMergeLevelMinusTwo = reader.ReadUnsignedExpGolomb();
if (parallelMergeLevelMinusTwo > sequenceParameterSet.CodingTreeBlockLog2 - 2)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid parallel merge level.");
}
this.ParallelMergeLevelLog2 = (int)parallelMergeLevelMinusTwo + 2;
this.SliceSegmentHeaderExtensionPresent = reader.ReadFlag();
this.MaxTransformSkipBlockLog2 = 2;
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 picture extensions are not supported for still-image items.");
}
if (extensionFlags[0])
{
this.ReadRangeExtension(ref reader);
}
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 picture-parameter-set identifier.</summary>
public byte Id { get; }
/// <summary>Gets the referenced sequence-parameter-set identifier.</summary>
public byte SequenceParameterSetId { get; }
/// <summary>Gets the sequence parameters governing this picture parameter set.</summary>
public HevcSequenceParameterSet SequenceParameterSet { get; }
/// <summary>Gets a value indicating whether dependent slice segments can occur.</summary>
public bool DependentSliceSegmentsEnabled { get; }
/// <summary>Gets a value indicating whether slice headers contain the picture-output flag.</summary>
public bool OutputFlagPresent { get; }
/// <summary>Gets the number of reserved extra bits at the start of each independent slice header.</summary>
public int ExtraSliceHeaderBitCount { get; }
/// <summary>Gets a value indicating whether transform-coefficient sign hiding is enabled.</summary>
public bool SignDataHidingEnabled { get; }
/// <summary>Gets a value indicating whether slices can select an alternate CABAC initialization table.</summary>
public bool CabacInitializationPresent { get; }
/// <summary>Gets the default active reference-index count for reference list zero.</summary>
public int DefaultReferenceIndexCountList0 { get; }
/// <summary>Gets the default active reference-index count for reference list one.</summary>
public int DefaultReferenceIndexCountList1 { get; }
/// <summary>Gets the picture quantization-parameter initializer relative to 26.</summary>
public int InitialQuantizationParameterMinus26 { get; }
/// <summary>Gets a value indicating whether inter-coded neighbors are excluded from intra prediction.</summary>
public bool ConstrainedIntraPredictionEnabled { get; }
/// <summary>Gets a value indicating whether residual transform skipping can be selected.</summary>
public bool TransformSkipEnabled { get; }
/// <summary>Gets a value indicating whether coding units can change the quantization parameter.</summary>
public bool CodingUnitQuantizationParameterDeltaEnabled { get; }
/// <summary>Gets the coding-tree depth at which quantization-parameter deltas are signaled.</summary>
public int QuantizationParameterDeltaDepth { get; }
/// <summary>Gets the picture-level Cb quantization-parameter offset.</summary>
public int ChromaCbQuantizationParameterOffset { get; }
/// <summary>Gets the picture-level Cr quantization-parameter offset.</summary>
public int ChromaCrQuantizationParameterOffset { get; }
/// <summary>Gets a value indicating whether slices can add Cb and Cr quantization-parameter offsets.</summary>
public bool SliceChromaQuantizationParameterOffsetsPresent { get; }
/// <summary>Gets a value indicating whether weighted prediction can be used by predictive slices.</summary>
public bool WeightedPredictionEnabled { get; }
/// <summary>Gets a value indicating whether weighted prediction can be used by bidirectional slices.</summary>
public bool WeightedBiPredictionEnabled { get; }
/// <summary>Gets a value indicating whether coding units can bypass transform and quantization.</summary>
public bool TransquantizationBypassEnabled { get; }
/// <summary>Gets a value indicating whether the coded picture is partitioned into tiles.</summary>
public bool TilesEnabled { get; }
/// <summary>Gets a value indicating whether wavefront entropy-coding synchronization is enabled.</summary>
public bool EntropyCodingSynchronizationEnabled { get; }
/// <summary>Gets a value indicating whether the tile grid uses uniform proportional spacing.</summary>
public bool UniformTileSpacing { get; }
/// <summary>Gets the tile-column widths in coding-tree blocks.</summary>
public IReadOnlyList<int> TileColumnWidths { get; }
/// <summary>Gets the tile-row heights in coding-tree blocks.</summary>
public IReadOnlyList<int> TileRowHeights { get; }
/// <summary>Gets a value indicating whether in-loop filtering crosses tile boundaries.</summary>
public bool LoopFilterAcrossTilesEnabled { get; }
/// <summary>Gets a value indicating whether in-loop filtering crosses slice boundaries.</summary>
public bool LoopFilterAcrossSlicesEnabled { get; }
/// <summary>Gets a value indicating whether picture or slice syntax controls deblocking.</summary>
public bool DeblockingFilterControlPresent { get; }
/// <summary>Gets a value indicating whether slice headers can override picture-level deblocking.</summary>
public bool DeblockingFilterOverrideEnabled { get; }
/// <summary>Gets a value indicating whether deblocking is disabled by default for the picture.</summary>
public bool DeblockingFilterDisabled { get; }
/// <summary>Gets half the picture-level deblocking beta-threshold offset.</summary>
public int DeblockingFilterBetaOffsetDiv2 { get; }
/// <summary>Gets half the picture-level deblocking clipping-threshold offset.</summary>
public int DeblockingFilterTcOffsetDiv2 { get; }
/// <summary>Gets a value indicating whether this picture parameter set supplies scaling-list data.</summary>
public bool ScalingListDataPresent { get; }
/// <summary>Gets the effective quantization scaling matrices for slices using this picture parameter set.</summary>
public HevcScalingList ScalingList { get; }
/// <summary>Gets a value indicating whether slice headers can modify the initial reference-picture lists.</summary>
public bool ReferenceListModificationPresent { get; }
/// <summary>Gets the base-two logarithm of the parallel merge-estimation region width and height.</summary>
public int ParallelMergeLevelLog2 { get; }
/// <summary>Gets a value indicating whether slice-segment headers carry extension bytes.</summary>
public bool SliceSegmentHeaderExtensionPresent { get; }
/// <summary>Gets the base-two logarithm of the maximum transform-skip block width and height.</summary>
public int MaxTransformSkipBlockLog2 { get; private set; }
/// <summary>Gets a value indicating whether cross-component residual prediction is enabled.</summary>
public bool CrossComponentPredictionEnabled { get; private set; }
/// <summary>Gets the coding-tree depth at which chroma quantization-offset indices are signaled.</summary>
public int ChromaQuantizationParameterOffsetDepth { get; private set; }
/// <summary>Gets the Cb offsets in the selectable chroma quantization-parameter offset list.</summary>
public IReadOnlyList<int> ChromaQuantizationParameterOffsetsCb { get; private set; } = Array.Empty<int>();
/// <summary>Gets the Cr offsets in the selectable chroma quantization-parameter offset list.</summary>
public IReadOnlyList<int> ChromaQuantizationParameterOffsetsCr { get; private set; } = Array.Empty<int>();
/// <summary>Gets the base-two logarithm of the luma sample-adaptive-offset value scale.</summary>
public int SampleAdaptiveOffsetScaleLumaLog2 { get; private set; }
/// <summary>Gets the base-two logarithm of the chroma sample-adaptive-offset value scale.</summary>
public int SampleAdaptiveOffsetScaleChromaLog2 { get; private set; }
/// <summary>
/// Reads the Range Extensions fields that change transform, chroma quantization, and SAO reconstruction.
/// </summary>
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
/// <exception cref="InvalidImageContentException">
/// A transform, coding-tree-depth, chroma offset, or sample-adaptive-offset scale is outside the governing
/// sequence-parameter-set bounds.
/// </exception>
private void ReadRangeExtension(ref HevcBitReader reader)
{
if (this.TransformSkipEnabled)
{
uint maxTransformSkipBlockLog2MinusTwo = reader.ReadUnsignedExpGolomb();
if (maxTransformSkipBlockLog2MinusTwo > this.SequenceParameterSet.MaxTransformBlockLog2 - 2)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid transform-skip block size.");
}
this.MaxTransformSkipBlockLog2 = (int)maxTransformSkipBlockLog2MinusTwo + 2;
}
this.CrossComponentPredictionEnabled = reader.ReadFlag();
if (reader.ReadFlag())
{
uint chromaOffsetDepth = reader.ReadUnsignedExpGolomb();
int maximumDepth = this.SequenceParameterSet.CodingTreeBlockLog2 - this.SequenceParameterSet.MinCodingBlockLog2;
if (chromaOffsetDepth > maximumDepth)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid chroma quantization-offset depth.");
}
this.ChromaQuantizationParameterOffsetDepth = (int)chromaOffsetDepth;
uint chromaOffsetCountMinusOne = reader.ReadUnsignedExpGolomb();
if (chromaOffsetCountMinusOne > 5)
{
throw new InvalidImageContentException("The HEVC picture parameter set declares too many chroma quantization offsets.");
}
int chromaOffsetCount = (int)chromaOffsetCountMinusOne + 1;
int[] cbOffsets = new int[chromaOffsetCount];
int[] crOffsets = new int[chromaOffsetCount];
for (int offset = 0; offset < chromaOffsetCount; offset++)
{
cbOffsets[offset] = ReadQuantizationParameterOffset(ref reader);
crOffsets[offset] = ReadQuantizationParameterOffset(ref reader);
}
this.ChromaQuantizationParameterOffsetsCb = cbOffsets;
this.ChromaQuantizationParameterOffsetsCr = crOffsets;
}
uint lumaScale = reader.ReadUnsignedExpGolomb();
uint chromaScale = reader.ReadUnsignedExpGolomb();
int maximumLumaScale = Math.Max(this.SequenceParameterSet.BitDepthLuma, 10) - 10;
int maximumChromaScale = Math.Max(this.SequenceParameterSet.BitDepthChroma, 10) - 10;
if (lumaScale > maximumLumaScale || chromaScale > maximumChromaScale)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid sample-adaptive-offset scale.");
}
this.SampleAdaptiveOffsetScaleLumaLog2 = (int)lumaScale;
this.SampleAdaptiveOffsetScaleChromaLog2 = (int)chromaScale;
}
/// <summary>
/// Reads a signed chroma quantization-parameter offset.
/// </summary>
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
/// <returns>The decoded offset in the registered range from negative twelve through twelve.</returns>
/// <exception cref="InvalidImageContentException">The offset is outside its registered range.</exception>
private static int ReadQuantizationParameterOffset(ref HevcBitReader reader)
{
int offset = reader.ReadSignedExpGolomb();
if (offset is < -12 or > 12)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid chroma quantization-parameter offset.");
}
return offset;
}
/// <summary>
/// Reads a signed picture-level deblocking-filter offset.
/// </summary>
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
/// <returns>The decoded half-offset in the registered range from negative six through six.</returns>
/// <exception cref="InvalidImageContentException">The offset is outside its registered range.</exception>
private static int ReadDeblockingFilterOffset(ref HevcBitReader reader)
{
int offset = reader.ReadSignedExpGolomb();
if (offset is < -6 or > 6)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid deblocking-filter offset.");
}
return offset;
}
/// <summary>
/// Gets the number of coding-tree blocks needed to cover one coded picture dimension.
/// </summary>
/// <param name="sampleCount">The coded luma-sample count.</param>
/// <param name="codingTreeBlockLog2">The base-two logarithm of the coding-tree-block size.</param>
/// <returns>The covering coding-tree-block count.</returns>
private static int GetCodingTreeBlockCount(int sampleCount, int codingTreeBlockLog2)
=> ((sampleCount - 1) >> codingTreeBlockLog2) + 1;
/// <summary>
/// Reads or derives one axis of the tile grid.
/// </summary>
/// <param name="reader">The picture-parameter-set raw byte sequence payload reader.</param>
/// <param name="codingTreeBlockCount">The complete picture dimension in coding-tree blocks.</param>
/// <param name="tileCount">The tile count on the same axis.</param>
/// <param name="uniformSpacing">Whether the widths or heights use proportional uniform spacing.</param>
/// <returns>Every tile width or height in coding-tree blocks, including the inferred final tile.</returns>
/// <exception cref="InvalidImageContentException">An explicit tile consumes the final block required by a later tile.</exception>
private static int[] ReadTileDimensions(
ref HevcBitReader reader,
int codingTreeBlockCount,
int tileCount,
bool uniformSpacing)
{
int[] dimensions = new int[tileCount];
if (uniformSpacing)
{
for (int tile = 0; tile < tileCount; tile++)
{
// The normative floor-difference formula assigns every CTB exactly once even when the picture
// dimension is not divisible by the number of tiles.
dimensions[tile] = (((tile + 1) * codingTreeBlockCount) / tileCount)
- ((tile * codingTreeBlockCount) / tileCount);
}
return dimensions;
}
int consumed = 0;
for (int tile = 0; tile < tileCount - 1; tile++)
{
uint dimensionMinusOne = reader.ReadUnsignedExpGolomb();
if (dimensionMinusOne >= codingTreeBlockCount - consumed - 1)
{
throw new InvalidImageContentException("The HEVC picture parameter set has an invalid explicit tile dimension.");
}
dimensions[tile] = (int)dimensionMinusOne + 1;
consumed += dimensions[tile];
}
dimensions[^1] = codingTreeBlockCount - consumed;
return dimensions;
}
}
Loading…
Cancel
Save