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))