From d4e9b71dba5183790cf62232dd668f6bd199ceb4 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 26 Aug 2026 01:46:38 +1000 Subject: [PATCH] Implement HEVC quantization parameter derivation --- HEIF_IMPLEMENTATION_PLAN.md | 2 + .../Heif/Hevc/HevcQuantizationParameters.cs | 99 +++++++++++++++++++ .../Hevc/HevcQuantizationParametersTests.cs | 82 +++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Hevc/HevcQuantizationParameters.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcQuantizationParametersTests.cs diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 2b6e0d156..b1ec8340f 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -471,6 +471,8 @@ Implement and verify in dependency order: - [x] Implement allocation-free SIMD-first inverse DCT for every 4/8/16/32 width and height combination, the four-by-four intra-luma inverse DST, normative intermediate and residual clipping, and saturated prediction addition for 8/10/12-bit samples. Verify normal and forced-scalar execution against fixed results and a dense scalar oracle. - [x] Implement the SIMD-first inverse-quantization primitive for flat and scaling-list paths across every transform size, component matrix, prediction mode, transform-skip scaling rule, signed normalization direction, and range-extension precision path. - [ ] Derive effective luma and chroma quantization parameters, including bit-depth offsets and chroma-format mapping, then connect scaling-list selection and inverse quantization to transform-unit traversal. + - [x] Implement the immutable effective-QP value used by reconstruction, including independent luma/chroma bit-depth offsets, the normative 4:2:0 mapping plateaus, the 4:2:2/4:4:4 saturation rule, and combined picture/slice/coding-unit chroma offsets. + - [ ] Select each transform unit's coding-unit luma QP and chroma-adjustment-list entry, then pass the derived component QP into inverse quantization. - [ ] Implement transform skip, coefficient rotation, implicit and explicit residual DPCM, transquant bypass, and lossless reconstruction. - [ ] Connect coefficient decoding, inverse quantization, transform selection, reusable scratch, and add/clip to transform-unit traversal. - [ ] Deblocking and sample-adaptive offset for every signaled luma/chroma and bit-depth path. diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcQuantizationParameters.cs new file mode 100644 index 000000000..4647b0867 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcQuantizationParameters.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Contains the effective HEVC quantization parameters for one transform unit. +/// +internal readonly struct HevcQuantizationParameters +{ + /// + /// Initializes a new instance of the struct. + /// + /// The effective coding-unit luma quantization parameter before the luma bit-depth offset. + /// The reconstructed luma precision. + /// The reconstructed chroma precision. + /// The sequence chroma-format identifier. + /// The combined picture, slice, and coding-unit Cb quantization-parameter offset. + /// The combined picture, slice, and coding-unit Cr quantization-parameter offset. + public HevcQuantizationParameters( + int lumaQuantizationParameter, + int lumaBitDepth, + int chromaBitDepth, + byte chromaFormat, + int cbQuantizationParameterOffset, + int crQuantizationParameterOffset) + { + int lumaBitDepthOffset = 6 * (lumaBitDepth - 8); + int chromaBitDepthOffset = 6 * (chromaBitDepth - 8); + this.Luma = lumaQuantizationParameter + lumaBitDepthOffset; + this.Cb = GetChromaQuantizationParameter(lumaQuantizationParameter, cbQuantizationParameterOffset, chromaBitDepthOffset, chromaFormat); + this.Cr = GetChromaQuantizationParameter(lumaQuantizationParameter, crQuantizationParameterOffset, chromaBitDepthOffset, chromaFormat); + } + + /// + /// Gets the effective nonnegative luma quantization parameter including its bit-depth offset. + /// + public int Luma { get; } + + /// + /// Gets the effective nonnegative blue-difference chroma quantization parameter including its bit-depth offset. + /// + public int Cb { get; } + + /// + /// Gets the effective nonnegative red-difference chroma quantization parameter including its bit-depth offset. + /// + public int Cr { get; } + + /// + /// Gets the H.265 Table 8-10 chroma quantization-parameter mapping for 4:2:0 pictures. + /// + private static ReadOnlySpan Chroma420QuantizationParameterMap => + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + ]; + + /// + /// Gets the effective quantization parameter for the selected reconstruction plane. + /// + /// The reconstruction plane. + /// The effective nonnegative quantization parameter including its bit-depth offset. + public int Get(HevcPlane plane) => plane switch + { + HevcPlane.Y => this.Luma, + HevcPlane.Cb => this.Cb, + _ => this.Cr, + }; + + /// + /// Derives an effective chroma quantization parameter from the luma value and combined component offset. + /// + /// The effective coding-unit luma quantization parameter before its bit-depth offset. + /// The combined picture, slice, and coding-unit component offset. + /// Six times the number of chroma bits above eight. + /// The sequence chroma-format identifier. + /// The effective nonnegative chroma quantization parameter including its bit-depth offset. + private static int GetChromaQuantizationParameter( + int lumaQuantizationParameter, + int componentOffset, + int chromaBitDepthOffset, + byte chromaFormat) + { + int unscaled = Math.Clamp(lumaQuantizationParameter + componentOffset, -chromaBitDepthOffset, 57); + if (unscaled < 0) + { + return unscaled + chromaBitDepthOffset; + } + + // H.265 section 8.6.1 maps nonnegative chroma QP before adding the bit-depth offset. The 4:2:0 table + // contains plateaus above QP 29, whereas 4:2:2 and 4:4:4 remain linear through 51 and then saturate. + int mapped = chromaFormat == 1 + ? Chroma420QuantizationParameterMap[unscaled] + : Math.Min(unscaled, 51); + + return mapped + chromaBitDepthOffset; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcQuantizationParametersTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcQuantizationParametersTests.cs new file mode 100644 index 000000000..409271cff --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcQuantizationParametersTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Hevc; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; + +/// +/// Verifies HEVC luma and chroma quantization-parameter derivation. +/// +[Trait("Format", "Heic")] +public class HevcQuantizationParametersTests +{ + /// + /// Verifies that luma quantization parameters include the precision-derived offset. + /// + /// The luma quantization parameter before the bit-depth offset. + /// The reconstructed luma precision. + /// The expected effective luma quantization parameter. + [Theory] + [InlineData(22, 8, 22)] + [InlineData(-12, 10, 0)] + [InlineData(-24, 12, 0)] + [InlineData(51, 12, 75)] + public void LumaIncludesBitDepthOffset(int quantizationParameter, int bitDepth, int expected) + { + HevcQuantizationParameters parameters = new(quantizationParameter, bitDepth, bitDepth, 1, 0, 0); + + Assert.Equal(expected, parameters.Luma); + Assert.Equal(expected, parameters.Get(HevcPlane.Y)); + } + + /// + /// Verifies the 4:2:0 chroma mapping plateaus and the upper mapped value. + /// + /// The luma quantization parameter before component offsets. + /// The expected effective eight-bit chroma quantization parameter. + [Theory] + [InlineData(29, 29)] + [InlineData(30, 29)] + [InlineData(35, 33)] + [InlineData(37, 34)] + [InlineData(43, 37)] + [InlineData(51, 45)] + public void Chroma420UsesNormativeMapping(int quantizationParameter, int expected) + { + HevcQuantizationParameters parameters = new(quantizationParameter, 8, 8, 1, 0, 0); + + Assert.Equal(expected, parameters.Cb); + Assert.Equal(expected, parameters.Cr); + } + + /// + /// Verifies that 4:2:2 and 4:4:4 chroma quantization parameters are linear through 51 and saturate above it. + /// + /// The tested sequence chroma-format identifier. + [Theory] + [InlineData((byte)2)] + [InlineData((byte)3)] + public void FullResolutionMappingsSaturateAboveFiftyOne(byte chromaFormat) + { + HevcQuantizationParameters parameters = new(51, 8, 8, chromaFormat, 6, 6); + + Assert.Equal(51, parameters.Cb); + Assert.Equal(51, parameters.Cr); + } + + /// + /// Verifies negative chroma values and independent combined component offsets at higher precision. + /// + [Fact] + public void ChromaAppliesCombinedOffsetsAndBitDepthOffset() + { + HevcQuantizationParameters parameters = new(-8, 10, 12, 1, -8, 20); + + Assert.Equal(4, parameters.Luma); + Assert.Equal(8, parameters.Cb); + Assert.Equal(36, parameters.Cr); + Assert.Equal(parameters.Cb, parameters.Get(HevcPlane.Cb)); + Assert.Equal(parameters.Cr, parameters.Get(HevcPlane.Cr)); + } +}