diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs index 414d28f92..d5c91dc37 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs @@ -5,14 +5,28 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +/// +/// Stores the AV1 DC and AC dequantization values for every segment and color plane in a frame. +/// internal class Av1DeQuantizationContext { + /// + /// The DC dequantization values indexed by segment and then plane. + /// private readonly short[][] dcContent; + + /// + /// The AC dequantization values indexed by segment and then plane. + /// private readonly short[][] acContent; - /// - /// SVT: svt_aom_setup_segmentation_dequant - /// + /// + /// Initializes a new instance of the class from the frame's base quantizer, + /// segment adjustments, plane deltas, and coded bit depth. + /// + /// The sequence header that supplies the coded bit depth. + /// The frame header that supplies segmentation and quantization parameters. + /// SVT-AV1: svt_aom_setup_segmentation_dequant. public Av1DeQuantizationContext(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { Av1BitDepth bitDepth = sequenceHeader.ColorConfig.BitDepth; @@ -35,15 +49,39 @@ internal class Av1DeQuantizationContext } } + /// + /// Gets the DC dequantization value for a segment and color plane. + /// + /// The zero-based AV1 segment identifier. + /// The color plane. + /// The DC dequantization value. public short GetDc(int segmentId, Av1Plane plane) => this.dcContent[segmentId][(int)plane]; + /// + /// Gets the AC dequantization value for a segment and color plane. + /// + /// The zero-based AV1 segment identifier. + /// The color plane. + /// The AC dequantization value. public short GetAc(int segmentId, Av1Plane plane) => this.acContent[segmentId][(int)plane]; + /// + /// Sets the AC dequantization value for a segment and color plane. + /// + /// The zero-based AV1 segment identifier. + /// The color plane. + /// The AC dequantization value. public void SetAc(int segmentId, Av1Plane plane, short value) - => this.dcContent[segmentId][(int)plane] = value; + => this.acContent[segmentId][(int)plane] = value; + /// + /// Sets the DC dequantization value for a segment and color plane. + /// + /// The zero-based AV1 segment identifier. + /// The color plane. + /// The DC dequantization value. public void SetDc(int segmentId, Av1Plane plane, short value) => this.dcContent[segmentId][(int)plane] = value; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs index c2de37beb..b48f9ca12 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizationLookup.cs @@ -6,22 +6,23 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +/// +/// Provides the normative AV1 inverse quantization matrices for each matrix level, plane class, and transform size. +/// internal class Av1InverseQuantizationLookup { - // AV1 reuses the adjusted matrix for 64-pixel transform dimensions, while the stored tables omit those duplicate entries. + /// + /// Maps each AV1 transform size to its stored matrix index; sizes with a 64-pixel dimension reuse the adjusted 32-pixel matrix. + /// private static readonly byte[] TransformMatrixIndices = [ 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 3, 3, 10, 11, 12, 13, 8, 9 ]; /// - /// Gets 16 sets of quantization matrices for chroma and luma and each TX size. - /// Matrices for different TX sizes are in fact sub-sampled from the 32x32 and 16x16 sizes, - /// but explicitly defined here for convenience. Intra and inter matrix sets are the - /// same but changing DEFAULT_QM_INTER_OFFSET from zero allows for different matrices - /// for inter and intra blocks in the same frame. - /// Matrices for different QM levels have been rescaled in the frequency domain according - /// to different nominal viewing distances. + /// The inverse matrix weights indexed by matrix level, plane class, adjusted transform-size index, and raster coefficient. + /// Luma and chroma have separate matrices; U and V share the chroma set. Size-specific matrices are subsampled from the + /// normative 32x32 and 16x16 bases, and matrix levels represent different frequency-domain weighting strengths. /// private static readonly int[][][][] InverseWeightTable = [ @@ -6802,9 +6803,18 @@ internal class Av1InverseQuantizationLookup ] ]; + /// + /// Gets the inverse quantization matrix for a matrix level, color plane, and transform size. + /// + /// The quantization-matrix level. + /// The color plane; U and V select the shared chroma matrix. + /// The transform size whose raster coefficient weights are requested. + /// The inverse matrix weights in raster coefficient order. public static ReadOnlySpan GetQuantizationMatrix(int level, Av1Plane plane, Av1TransformSize transformSize) { int[][][] levelMatrices = InverseWeightTable[level]; + + // The table stores one luma plane class and one shared chroma plane class. int[][] planeMatrices = levelMatrices[Math.Min(1, (int)plane)]; int transformMatrixIndex = TransformMatrixIndices[(int)transformSize]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs index 94616a802..b1983438f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -7,12 +7,31 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +/// +/// Reconstructs AV1 transform coefficients from quantized coefficient levels. +/// internal class Av1InverseQuantizer { + /// + /// The sequence-level color configuration that determines coefficient precision. + /// private readonly ObuSequenceHeader sequenceHeader; + + /// + /// The frame-level segmentation and quantization configuration. + /// private readonly ObuFrameHeader frameHeader; + + /// + /// The current per-segment, per-plane dequantization values, including any superblock delta-Q update. + /// private Av1DeQuantizationContext deQuantsDeltaQ; + /// + /// Initializes a new instance of the class. + /// + /// The sequence header that supplies coded bit depth and color configuration. + /// The frame header that supplies segmentation and quantization parameters. public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { this.sequenceHeader = sequenceHeader; @@ -20,6 +39,11 @@ internal class Av1InverseQuantizer this.deQuantsDeltaQ = new(sequenceHeader, frameHeader); } + /// + /// Updates the active dequantization context for a superblock, applying its delta-Q value when signaled. + /// + /// The frame dequantization context to update and retain. + /// The superblock whose quantizer adjustment is applied. public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo) { Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth; @@ -44,19 +68,32 @@ internal class Av1InverseQuantizer } /// - /// SVT: svt_aom_inverse_quantize + /// Converts scan-ordered quantized levels into clamped, raster-ordered transform coefficients. /// + /// The block mode information containing the active segment identifier. + /// The packed coefficient buffer: the first element is the coefficient count and the remaining elements are scan-ordered levels. + /// The destination for raster-ordered dequantized coefficients. + /// The transform type that selects the coefficient scan and matrix class. + /// The transform dimensions and scale. + /// The color plane whose quantizer and matrix are used. + /// The number of coefficient levels consumed. + /// SVT-AV1: svt_aom_inverse_quantize. public int InverseQuantize(Av1BlockModeInfo mode, Span level, Span qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane) { Guard.NotNull(this.deQuantsDeltaQ); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scanIndices = scanOrder.Scan; + + // AV1 bounds reconstructed coefficients to a signed range with seven headroom bits beyond pixel precision. int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1; int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())); bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix; bool lossless = this.frameHeader.LosslessArray[mode.SegmentId]; short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane); short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane); + + // The final matrix level is flat. Lossless blocks, frames without matrices, and one-dimensional transforms + // must use it so coefficient frequency does not change the signaled dequantization value. int qmLevel = lossless || !usingQuantizationMatrix ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : this.frameHeader.SegmentationParameters.QMLevel[(int)plane][mode.SegmentId]; @@ -67,6 +104,7 @@ internal class Av1InverseQuantizer int shift = transformSize.GetScale(); + // Entropy decoding stores the populated coefficient count in the leading slot and the levels after it. int coefficientCount = level[0]; level = level[1..]; int lev = level[0]; @@ -74,6 +112,8 @@ internal class Av1InverseQuantizer if (lev != 0) { int pos = scanIndices[0]; + + // Preserve the AV1 24-bit dequantization intermediate before removing transform-size scaling. qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, iqMatrix)) & 0xffffff); qCoefficient >>= shift; @@ -91,6 +131,8 @@ internal class Av1InverseQuantizer if (lev != 0) { int pos = scanIndices[i]; + + // AC levels arrive in entropy scan order but the inverse transform consumes raster positions. qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, iqMatrix)) & 0xffffff); qCoefficient >>= shift; @@ -107,10 +149,16 @@ internal class Av1InverseQuantizer } /// - /// SVT: get_dqv + /// Applies an inverse quantization-matrix weight to a plane dequantization value. /// + /// The unweighted DC or AC dequantization value. + /// The raster coefficient index into the inverse matrix. + /// The inverse quantization matrix for the current level, plane, and transform size. + /// The matrix-weighted dequantization value. + /// SVT-AV1: get_dqv. private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ReadOnlySpan iqMatrix) { + // Matrix elements use fixed-point precision; adding half a unit produces nearest-integer rounding on shift. const int bias = 1 << (Av1Constants.QuantizationMatrixElementBitCount - 1); int deQuantifiedValue = dequant; diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs index 205f1f3a1..0f9773702 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs @@ -5,6 +5,9 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +/// +/// Provides the normative AV1 DC and AC dequantization values for each quantizer index and supported bit depth. +/// internal class Av1QuantizationLookup { // Coefficient scaling and quantization with AV1 TX are tailored to @@ -35,6 +38,10 @@ internal class Av1QuantizationLookup // expects quantizers to be larger for higher-bitdepth input. In // addition, the minimum allowable quantizer is 4; smaller values will // underflow to 0 in the actual quantization routines. + + /// + /// The Q3 AC dequantization values for 8-bit samples, indexed by quantizer index. + /// private static readonly short[] AcQlookup8 = [ 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, @@ -52,6 +59,9 @@ internal class Av1QuantizationLookup 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, ]; + /// + /// The Q3 AC dequantization values for 10-bit samples, indexed by quantizer index. + /// private static readonly short[] AcQlookup10 = [ 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, @@ -69,6 +79,9 @@ internal class Av1QuantizationLookup 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, ]; + /// + /// The Q3 AC dequantization values for 12-bit samples, indexed by quantizer index. + /// private static readonly short[] AcQlookup12 = [ 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, @@ -88,6 +101,9 @@ internal class Av1QuantizationLookup 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247, ]; + /// + /// The Q3 DC dequantization values for 8-bit samples, indexed by quantizer index. + /// private static readonly short[] DcQlookup8 = [ 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, @@ -104,6 +120,9 @@ internal class Av1QuantizationLookup 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, ]; + /// + /// The Q3 DC dequantization values for 10-bit samples, indexed by quantizer index. + /// private static readonly short[] DcQlookup10 = [ 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, @@ -121,6 +140,9 @@ internal class Av1QuantizationLookup 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, ]; + /// + /// The Q3 DC dequantization values for 12-bit samples, indexed by quantizer index. + /// private static readonly short[] DcQlookup12 = [ 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, @@ -140,8 +162,16 @@ internal class Av1QuantizationLookup 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, ]; + /// + /// Gets the DC dequantization value after applying a plane delta to the frame quantizer index. + /// + /// The frame or segment quantizer index. + /// The signed DC quantizer adjustment for the selected plane. + /// The coded sample bit depth. + /// The Q3 DC dequantization value. public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) { + // Plane deltas may move beyond the signaled 8-bit quantizer domain, where AV1 requires endpoint clamping. int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); switch (bitDepth) { @@ -157,8 +187,16 @@ internal class Av1QuantizationLookup } } + /// + /// Gets the AC dequantization value after applying a plane delta to the frame quantizer index. + /// + /// The frame or segment quantizer index. + /// The signed AC quantizer adjustment for the selected plane. + /// The coded sample bit depth. + /// The Q3 AC dequantization value. public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) { + // Plane deltas may move beyond the signaled 8-bit quantizer domain, where AV1 requires endpoint clamping. int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); switch (bitDepth) { @@ -174,6 +212,13 @@ internal class Av1QuantizationLookup } } + /// + /// Gets the quantizer index for a segment, including its alternative-quantizer feature when active. + /// + /// The frame segmentation configuration. + /// The zero-based AV1 segment identifier. + /// The frame's base quantizer index. + /// The segment quantizer index clamped to the AV1 quantizer domain. public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer))