diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index b1716d2c4..367cbd32d 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/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. | diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs index eb0d258ff..0879cec01 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcCodecConfiguration.cs @@ -199,6 +199,23 @@ internal sealed class HevcCodecConfiguration } this.SequenceParameterSets = sequenceParameterSets; + + List 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; } /// @@ -277,6 +294,11 @@ internal sealed class HevcCodecConfiguration /// public IReadOnlyList SequenceParameterSets { get; } + /// + /// Gets the validated picture parameter sets carried by the codec-configuration property. + /// + public IReadOnlyList PictureParameterSets { get; } + /// /// Validates the associated pixel-information property against the coded luma and chroma sample precisions. /// diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcPictureParameterSet.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcPictureParameterSet.cs new file mode 100644 index 000000000..43bd1b9e1 --- /dev/null +++ b/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; + +/// +/// Contains the HEVC picture fields required to decode the independently coded picture in one still-image item. +/// +internal sealed class HevcPictureParameterSet +{ + /// + /// Initializes a new instance of the class. + /// + /// The decoded picture-parameter-set NAL unit. + /// The sequence parameter sets available to the coded image item. + /// + /// The picture parameter set is malformed, references an unavailable sequence parameter set, or declares + /// picture geometry outside that sequence parameter set. + /// + public HevcPictureParameterSet( + HevcNalUnit nalUnit, + IReadOnlyList 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 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(); + } + + /// Gets the picture-parameter-set identifier. + public byte Id { get; } + + /// Gets the referenced sequence-parameter-set identifier. + public byte SequenceParameterSetId { get; } + + /// Gets the sequence parameters governing this picture parameter set. + public HevcSequenceParameterSet SequenceParameterSet { get; } + + /// Gets a value indicating whether dependent slice segments can occur. + public bool DependentSliceSegmentsEnabled { get; } + + /// Gets a value indicating whether slice headers contain the picture-output flag. + public bool OutputFlagPresent { get; } + + /// Gets the number of reserved extra bits at the start of each independent slice header. + public int ExtraSliceHeaderBitCount { get; } + + /// Gets a value indicating whether transform-coefficient sign hiding is enabled. + public bool SignDataHidingEnabled { get; } + + /// Gets a value indicating whether slices can select an alternate CABAC initialization table. + public bool CabacInitializationPresent { get; } + + /// Gets the default active reference-index count for reference list zero. + public int DefaultReferenceIndexCountList0 { get; } + + /// Gets the default active reference-index count for reference list one. + public int DefaultReferenceIndexCountList1 { get; } + + /// Gets the picture quantization-parameter initializer relative to 26. + public int InitialQuantizationParameterMinus26 { get; } + + /// Gets a value indicating whether inter-coded neighbors are excluded from intra prediction. + public bool ConstrainedIntraPredictionEnabled { get; } + + /// Gets a value indicating whether residual transform skipping can be selected. + public bool TransformSkipEnabled { get; } + + /// Gets a value indicating whether coding units can change the quantization parameter. + public bool CodingUnitQuantizationParameterDeltaEnabled { get; } + + /// Gets the coding-tree depth at which quantization-parameter deltas are signaled. + public int QuantizationParameterDeltaDepth { get; } + + /// Gets the picture-level Cb quantization-parameter offset. + public int ChromaCbQuantizationParameterOffset { get; } + + /// Gets the picture-level Cr quantization-parameter offset. + public int ChromaCrQuantizationParameterOffset { get; } + + /// Gets a value indicating whether slices can add Cb and Cr quantization-parameter offsets. + public bool SliceChromaQuantizationParameterOffsetsPresent { get; } + + /// Gets a value indicating whether weighted prediction can be used by predictive slices. + public bool WeightedPredictionEnabled { get; } + + /// Gets a value indicating whether weighted prediction can be used by bidirectional slices. + public bool WeightedBiPredictionEnabled { get; } + + /// Gets a value indicating whether coding units can bypass transform and quantization. + public bool TransquantizationBypassEnabled { get; } + + /// Gets a value indicating whether the coded picture is partitioned into tiles. + public bool TilesEnabled { get; } + + /// Gets a value indicating whether wavefront entropy-coding synchronization is enabled. + public bool EntropyCodingSynchronizationEnabled { get; } + + /// Gets a value indicating whether the tile grid uses uniform proportional spacing. + public bool UniformTileSpacing { get; } + + /// Gets the tile-column widths in coding-tree blocks. + public IReadOnlyList TileColumnWidths { get; } + + /// Gets the tile-row heights in coding-tree blocks. + public IReadOnlyList TileRowHeights { get; } + + /// Gets a value indicating whether in-loop filtering crosses tile boundaries. + public bool LoopFilterAcrossTilesEnabled { get; } + + /// Gets a value indicating whether in-loop filtering crosses slice boundaries. + public bool LoopFilterAcrossSlicesEnabled { get; } + + /// Gets a value indicating whether picture or slice syntax controls deblocking. + public bool DeblockingFilterControlPresent { get; } + + /// Gets a value indicating whether slice headers can override picture-level deblocking. + public bool DeblockingFilterOverrideEnabled { get; } + + /// Gets a value indicating whether deblocking is disabled by default for the picture. + public bool DeblockingFilterDisabled { get; } + + /// Gets half the picture-level deblocking beta-threshold offset. + public int DeblockingFilterBetaOffsetDiv2 { get; } + + /// Gets half the picture-level deblocking clipping-threshold offset. + public int DeblockingFilterTcOffsetDiv2 { get; } + + /// Gets a value indicating whether this picture parameter set supplies scaling-list data. + public bool ScalingListDataPresent { get; } + + /// Gets the effective quantization scaling matrices for slices using this picture parameter set. + public HevcScalingList ScalingList { get; } + + /// Gets a value indicating whether slice headers can modify the initial reference-picture lists. + public bool ReferenceListModificationPresent { get; } + + /// Gets the base-two logarithm of the parallel merge-estimation region width and height. + public int ParallelMergeLevelLog2 { get; } + + /// Gets a value indicating whether slice-segment headers carry extension bytes. + public bool SliceSegmentHeaderExtensionPresent { get; } + + /// Gets the base-two logarithm of the maximum transform-skip block width and height. + public int MaxTransformSkipBlockLog2 { get; private set; } + + /// Gets a value indicating whether cross-component residual prediction is enabled. + public bool CrossComponentPredictionEnabled { get; private set; } + + /// Gets the coding-tree depth at which chroma quantization-offset indices are signaled. + public int ChromaQuantizationParameterOffsetDepth { get; private set; } + + /// Gets the Cb offsets in the selectable chroma quantization-parameter offset list. + public IReadOnlyList ChromaQuantizationParameterOffsetsCb { get; private set; } = Array.Empty(); + + /// Gets the Cr offsets in the selectable chroma quantization-parameter offset list. + public IReadOnlyList ChromaQuantizationParameterOffsetsCr { get; private set; } = Array.Empty(); + + /// Gets the base-two logarithm of the luma sample-adaptive-offset value scale. + public int SampleAdaptiveOffsetScaleLumaLog2 { get; private set; } + + /// Gets the base-two logarithm of the chroma sample-adaptive-offset value scale. + public int SampleAdaptiveOffsetScaleChromaLog2 { get; private set; } + + /// + /// Reads the Range Extensions fields that change transform, chroma quantization, and SAO reconstruction. + /// + /// The picture-parameter-set raw byte sequence payload reader. + /// + /// A transform, coding-tree-depth, chroma offset, or sample-adaptive-offset scale is outside the governing + /// sequence-parameter-set bounds. + /// + 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; + } + + /// + /// Reads a signed chroma quantization-parameter offset. + /// + /// The picture-parameter-set raw byte sequence payload reader. + /// The decoded offset in the registered range from negative twelve through twelve. + /// The offset is outside its registered range. + 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; + } + + /// + /// Reads a signed picture-level deblocking-filter offset. + /// + /// The picture-parameter-set raw byte sequence payload reader. + /// The decoded half-offset in the registered range from negative six through six. + /// The offset is outside its registered range. + 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; + } + + /// + /// Gets the number of coding-tree blocks needed to cover one coded picture dimension. + /// + /// The coded luma-sample count. + /// The base-two logarithm of the coding-tree-block size. + /// The covering coding-tree-block count. + private static int GetCodingTreeBlockCount(int sampleCount, int codingTreeBlockLog2) + => ((sampleCount - 1) >> codingTreeBlockLog2) + 1; + + /// + /// Reads or derives one axis of the tile grid. + /// + /// The picture-parameter-set raw byte sequence payload reader. + /// The complete picture dimension in coding-tree blocks. + /// The tile count on the same axis. + /// Whether the widths or heights use proportional uniform spacing. + /// Every tile width or height in coding-tree blocks, including the inferred final tile. + /// An explicit tile consumes the final block required by a later tile. + 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; + } +}