Browse Source

Implement SIMD-first HEVC inverse quantization

pull/2633/head
James Jackson-South 2 months ago
parent
commit
b9cadd0a88
  1. 5
      HEIF_IMPLEMENTATION_PLAN.md
  2. 401
      src/ImageSharp/Formats/Heif/Hevc/HevcInverseQuantizer.cs
  3. 272
      src/ImageSharp/Formats/Heif/Hevc/HevcScalingList.cs
  4. 105
      tests/ImageSharp.Benchmarks/Codecs/Heif/HevcInverseQuantizationBenchmarks.cs
  5. 187
      tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcInverseQuantizerTests.cs

5
HEIF_IMPLEMENTATION_PLAN.md

@ -44,6 +44,7 @@ Checkboxes may be marked complete only when the implementation and the verificat
- [x] Implement sequential pooled HEVC conversion in both directions for monochrome, 4:2:0, 4:2:2, and 4:4:4; independent luma/chroma precision; 8/10/12-bit full/limited ranges; and all six progressive 4:2:0 chroma sample locations. Eight-bit paths use JPEG's optimized RGB plane contracts, while high-bit-depth paths retain 16-bit packed pixels.
- [x] Implement allocation-free SIMD-first HEVC intra prediction for all 35 modes, 4/8/16/32 blocks, and 8/10/12-bit samples, including three-tap and strong-bilinear reference filtering, negative-angle reference extension, luma boundary filters, and SIMD horizontal transposition. Verify the normal and forced-scalar paths against one scalar oracle.
- [x] Implement allocation-free reconstructed-plane reference collection and normative substitution for complete, empty, and partially available borders, including asymmetric 4:2:2 availability units and the HEVC mode/size/component filter-selection rules.
- [x] Implement allocation-free SIMD-first HEVC inverse quantization for flat and custom scaling lists, 4/8/16/32 transforms, transform-skip scaling rules, extended precision, signed normalization shifts, and transform-range clipping. Store compact and transform-sized scaling matrices in one contiguous parameter-set allocation so the hot path can widen consecutive weights without coordinate division.
- [ ] Derive each reference-unit availability flag from coding-tree, slice, tile, constrained-intra, and picture-boundary state, then connect prediction to transform-unit traversal and the bounded item decoder lifecycle.
- [x] Implement the shared SIMD-first YUV/CICP paths in both directions for AV1 and HEVC bit depths, chroma formats, ranges, matrix coefficients, transfer functions, color primaries, and chroma positions, with scalar fallback only after the available vector widths are exhausted.
- [ ] Complete the independently generated AV1 and HEVC conformance matrix for every supported color-signaling combination before closing the Phase 5 color exit gate.
@ -468,7 +469,8 @@ Implement and verify in dependency order:
- [ ] Build each availability flag from reconstructed-plane and coding state, and connect the predictor to transform-unit traversal.
- [ ] Scaling lists, inverse quantization, transform skip, every required inverse transform, range-extension precision, and lossless reconstruction.
- [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.
- [ ] Implement scaling-list selection and inverse quantization for every transform size, component, prediction mode, quantization parameter, and range-extension precision path.
- [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.
- [ ] 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.
@ -567,6 +569,7 @@ Tasks:
- [x] Add a permanent frame-wide HEVC intra-prediction benchmark. On .NET 10, SIMD planar, vertical-angular, and horizontal-angular prediction measured 155.2, 150.7, and 255.0 microseconds per padded 1920x1088 frame, compared with forced-scalar timings of 1.890, 1.081, and 0.979 milliseconds: 12.2, 7.2, and 3.8 times faster with zero managed allocations.
- [x] Add a permanent frame-wide HEVC reference-preparation benchmark. On .NET 10, complete and partially substituted borders measured 465.3 and 566.7 microseconds per 2,040-block padded 1920x1088 frame, compared with forced-scalar timings of 477.5 and 608.0 microseconds, with zero managed allocations.
- [x] Add a permanent frame-wide HEVC inverse-transform benchmark. On .NET 10, dense-coefficient 32x32 twelve-bit inverse DCT, transposition, and add/clip measured 3.643 milliseconds per padded 1920x1088 frame, compared with 45.23 milliseconds with hardware intrinsics disabled: 12.4 times faster with zero managed allocations.
- [x] Add a permanent frame-wide HEVC inverse-quantization benchmark. On .NET 10, dense 32x32 twelve-bit flat and scaling-list paths measured 69.98 and 238.4 microseconds per padded 1920x1088 frame, compared with forced-scalar timings of 1.469 and 1.609 milliseconds: 21.0 and 6.7 times faster with zero managed allocations. Pre-expanding the scaling matrices once reduced the SIMD scaling-list path from 4.974 milliseconds to 238.4 microseconds.
- [ ] Implement vector paths only for confirmed hot loops, using existing `Vector128`, `Vector256`, and `Vector512` helper and dispatch patterns where supported.
- [ ] Prioritize shared color conversion and pixel packing, chroma upsampling, inverse-transform add-and-clip, intra predictors, HEVC deblock/SAO, AV1 loop filter/CDEF/restoration, and contiguous grid copies.
- [ ] Benchmark the complete decode color pipeline on representative 8/10/12-bit AVIF and HEIC images with and without embedded ICC profiles. Report absolute end-to-end timings and allocations in addition to the isolated YUV/CICP and ICC stage costs.

401
src/ImageSharp/Formats/Heif/Hevc/HevcInverseQuantizer.cs

@ -0,0 +1,401 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
namespace SixLabors.ImageSharp.Formats.Heif.Hevc;
/// <summary>
/// Reconstructs dequantized HEVC transform coefficients.
/// </summary>
internal static class HevcInverseQuantizer
{
/// <summary>
/// Gets the inverse quantization scale selected by the quantization-parameter remainder.
/// </summary>
private static ReadOnlySpan<byte> InverseQuantizationScales => [40, 45, 51, 57, 64, 72];
/// <summary>
/// Dequantizes one square transform block using the effective component quantization parameter.
/// </summary>
/// <param name="quantized">The decoded quantized coefficients in raster order.</param>
/// <param name="destination">The destination dequantized coefficients in raster order.</param>
/// <param name="log2Size">The base-two logarithm of the transform-block side.</param>
/// <param name="bitDepth">The reconstructed component precision.</param>
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
/// <param name="quantizationParameter">The effective nonnegative component quantization parameter including its bit-depth offset.</param>
/// <param name="scalingListEnabled">Whether the governing sequence enables scaling lists.</param>
/// <param name="scalingList">The effective picture scaling matrices.</param>
/// <param name="plane">The reconstructed color plane.</param>
/// <param name="isIntraPredicted">Whether the transform block belongs to an intra-predicted coding unit.</param>
/// <param name="transformSkip">Whether the transform block bypasses the inverse transform.</param>
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended by the sequence.</param>
public static void Dequantize(
ReadOnlySpan<int> quantized,
Span<int> destination,
int log2Size,
int bitDepth,
int maxTransformDynamicRange,
int quantizationParameter,
bool scalingListEnabled,
HevcScalingList scalingList,
HevcPlane plane,
bool isIntraPredicted,
bool transformSkip,
bool extendedPrecisionProcessingEnabled)
{
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size));
DebugGuard.MustBeGreaterThanOrEqualTo(quantizationParameter, 0, nameof(quantizationParameter));
int size = 1 << log2Size;
int sampleCount = size * size;
DebugGuard.IsTrue(quantized.Length >= sampleCount, "The quantized coefficient span is shorter than the transform block.");
DebugGuard.IsTrue(destination.Length >= sampleCount, "The dequantized coefficient span is shorter than the transform block.");
int transformShift = maxTransformDynamicRange - bitDepth - log2Size;
if (transformSkip && extendedPrecisionProcessingEnabled)
{
transformShift = Math.Max(0, transformShift);
}
int quantizationParameterPer = quantizationParameter / 6;
int quantizationParameterRemainder = quantizationParameter % 6;
int inverseQuantizationScale = InverseQuantizationScales[quantizationParameterRemainder];
bool useScalingList = scalingListEnabled && (!transformSkip || log2Size == 2);
int rightShift = 6 - (transformShift + quantizationParameterPer) + (useScalingList ? 4 : 0);
int outputMinimum = -(1 << maxTransformDynamicRange);
int outputMaximum = (1 << maxTransformDynamicRange) - 1;
// The input clip is part of the normative dequantization process. Its right-shift dependency ensures the
// following signed 32-bit multiplication and optional left shift cannot overflow for any valid coefficient.
int scaleBits = useScalingList ? 15 : 7;
int targetInputBitDepth = Math.Min(maxTransformDynamicRange + 1, 32 + rightShift - scaleBits);
int inputMinimum = -(1 << (targetInputBitDepth - 1));
int inputMaximum = (1 << (targetInputBitDepth - 1)) - 1;
if (!useScalingList)
{
DequantizeUniform(
quantized[..sampleCount],
destination[..sampleCount],
inverseQuantizationScale,
rightShift,
inputMinimum,
inputMaximum,
outputMinimum,
outputMaximum);
return;
}
int sizeId = log2Size - 2;
int matrixId = (isIntraPredicted ? 0 : 3) + (int)plane;
ReadOnlySpan<byte> matrix = scalingList.GetExpandedMatrix(sizeId, matrixId);
DequantizeScalingList(
quantized[..sampleCount],
destination[..sampleCount],
matrix,
inverseQuantizationScale,
rightShift,
inputMinimum,
inputMaximum,
outputMinimum,
outputMaximum);
}
/// <summary>
/// Dequantizes coefficients using one uniform inverse-quantization scale.
/// </summary>
/// <param name="source">The complete quantized coefficient block.</param>
/// <param name="destination">The complete dequantized coefficient block.</param>
/// <param name="scale">The inverse-quantization scale.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
private static void DequantizeUniform(
ReadOnlySpan<int> source,
Span<int> destination,
int scale,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
ref int sourceBase = ref MemoryMarshal.GetReference(source);
ref int destinationBase = ref MemoryMarshal.GetReference(destination);
int i = 0;
if (Vector512.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector512<int>.Count;
Vector512<int> weights = Vector512.Create(scale);
for (; i <= oneVectorFromEnd; i += Vector512<int>.Count)
{
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)i);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
if (Vector256.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector256<int>.Count;
Vector256<int> weights = Vector256.Create(scale);
for (; i <= oneVectorFromEnd; i += Vector256<int>.Count)
{
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)i);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
if (Vector128.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector128<int>.Count;
Vector128<int> weights = Vector128.Create(scale);
for (; i <= oneVectorFromEnd; i += Vector128<int>.Count)
{
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)i);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
for (; i < source.Length; i++)
{
destination[i] = Dequantize(source[i], scale, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum);
}
}
/// <summary>
/// Dequantizes coefficients using the expanded scaling matrix selected for the transform block.
/// </summary>
/// <param name="source">The complete quantized coefficient block.</param>
/// <param name="destination">The complete dequantized coefficient block.</param>
/// <param name="matrix">The scaling matrix expanded to the transform dimensions.</param>
/// <param name="inverseQuantizationScale">The inverse-quantization scale.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
private static void DequantizeScalingList(
ReadOnlySpan<int> source,
Span<int> destination,
ReadOnlySpan<byte> matrix,
int inverseQuantizationScale,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
ref int sourceBase = ref MemoryMarshal.GetReference(source);
ref int destinationBase = ref MemoryMarshal.GetReference(destination);
ref byte matrixBase = ref MemoryMarshal.GetReference(matrix);
int i = 0;
if (Vector512.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector512<int>.Count;
for (; i <= oneVectorFromEnd; i += Vector512<int>.Count)
{
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)i);
Vector512<int> weights = LoadScalingWeightsVector512(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
if (Vector256.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector256<int>.Count;
for (; i <= oneVectorFromEnd; i += Vector256<int>.Count)
{
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)i);
Vector256<int> weights = LoadScalingWeightsVector256(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
if (Vector128.IsHardwareAccelerated)
{
int oneVectorFromEnd = source.Length - Vector128<int>.Count;
for (; i <= oneVectorFromEnd; i += Vector128<int>.Count)
{
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)i);
Vector128<int> weights = LoadScalingWeightsVector128(ref Unsafe.Add(ref matrixBase, i), inverseQuantizationScale);
Dequantize(values, weights, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum).StoreUnsafe(ref destinationBase, (nuint)i);
}
}
for (; i < source.Length; i++)
{
int weight = Unsafe.Add(ref matrixBase, i) * inverseQuantizationScale;
destination[i] = Dequantize(source[i], weight, rightShift, inputMinimum, inputMaximum, outputMinimum, outputMaximum);
}
}
/// <summary>
/// Loads and widens sixteen scaling coefficients for a 512-bit coefficient vector.
/// </summary>
/// <param name="source">The first scaling coefficient.</param>
/// <param name="scale">The inverse-quantization scale.</param>
/// <returns>The sixteen ordered dequantization weights.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<int> LoadScalingWeightsVector512(ref byte source, int scale)
{
Vector128<byte> packed = Vector128.LoadUnsafe(ref source);
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(packed);
Vector256<uint> lower32 = Vector256.Create(Vector128.WidenLower(lower16), Vector128.WidenUpper(lower16));
Vector256<uint> upper32 = Vector256.Create(Vector128.WidenLower(upper16), Vector128.WidenUpper(upper16));
return Vector512.Create(lower32, upper32).AsInt32() * Vector512.Create(scale);
}
/// <summary>
/// Loads and widens eight scaling coefficients for a 256-bit coefficient vector.
/// </summary>
/// <param name="source">The first scaling coefficient.</param>
/// <param name="scale">The inverse-quantization scale.</param>
/// <returns>The eight ordered dequantization weights.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> LoadScalingWeightsVector256(ref byte source, int scale)
{
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source);
Vector128<ushort> values16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte());
Vector256<uint> values32 = Vector256.Create(Vector128.WidenLower(values16), Vector128.WidenUpper(values16));
return values32.AsInt32() * Vector256.Create(scale);
}
/// <summary>
/// Loads and widens four scaling coefficients for a 128-bit coefficient vector.
/// </summary>
/// <param name="source">The first scaling coefficient.</param>
/// <param name="scale">The inverse-quantization scale.</param>
/// <returns>The four ordered dequantization weights.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> LoadScalingWeightsVector128(ref byte source, int scale)
{
uint packed = Unsafe.ReadUnaligned<uint>(ref source);
Vector128<ushort> values16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte());
Vector128<uint> values32 = Vector128.WidenLower(values16);
return values32.AsInt32() * Vector128.Create(scale);
}
/// <summary>
/// Dequantizes sixteen signed coefficients with independent scaling weights.
/// </summary>
/// <param name="values">The quantized coefficients.</param>
/// <param name="weights">The dequantization weights.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
/// <returns>The dequantized coefficients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector512<int> Dequantize(
Vector512<int> values,
Vector512<int> weights,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
values = Vector512.Clamp(values, Vector512.Create(inputMinimum), Vector512.Create(inputMaximum));
Vector512<int> result = values * weights;
result = rightShift > 0
? (result + Vector512.Create(1 << (rightShift - 1))) >> rightShift
: result << -rightShift;
return Vector512.Clamp(result, Vector512.Create(outputMinimum), Vector512.Create(outputMaximum));
}
/// <summary>
/// Dequantizes eight signed coefficients with independent scaling weights.
/// </summary>
/// <param name="values">The quantized coefficients.</param>
/// <param name="weights">The dequantization weights.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
/// <returns>The dequantized coefficients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<int> Dequantize(
Vector256<int> values,
Vector256<int> weights,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
values = Vector256.Clamp(values, Vector256.Create(inputMinimum), Vector256.Create(inputMaximum));
Vector256<int> result = values * weights;
result = rightShift > 0
? (result + Vector256.Create(1 << (rightShift - 1))) >> rightShift
: result << -rightShift;
return Vector256.Clamp(result, Vector256.Create(outputMinimum), Vector256.Create(outputMaximum));
}
/// <summary>
/// Dequantizes four signed coefficients with independent scaling weights.
/// </summary>
/// <param name="values">The quantized coefficients.</param>
/// <param name="weights">The dequantization weights.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
/// <returns>The dequantized coefficients.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<int> Dequantize(
Vector128<int> values,
Vector128<int> weights,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
values = Vector128.Clamp(values, Vector128.Create(inputMinimum), Vector128.Create(inputMaximum));
Vector128<int> result = values * weights;
result = rightShift > 0
? (result + Vector128.Create(1 << (rightShift - 1))) >> rightShift
: result << -rightShift;
return Vector128.Clamp(result, Vector128.Create(outputMinimum), Vector128.Create(outputMaximum));
}
/// <summary>
/// Dequantizes one signed coefficient.
/// </summary>
/// <param name="value">The quantized coefficient.</param>
/// <param name="weight">The dequantization weight.</param>
/// <param name="rightShift">The signed normalization shift.</param>
/// <param name="inputMinimum">The inclusive quantized input minimum.</param>
/// <param name="inputMaximum">The inclusive quantized input maximum.</param>
/// <param name="outputMinimum">The inclusive dequantized output minimum.</param>
/// <param name="outputMaximum">The inclusive dequantized output maximum.</param>
/// <returns>The dequantized coefficient.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Dequantize(
int value,
int weight,
int rightShift,
int inputMinimum,
int inputMaximum,
int outputMinimum,
int outputMaximum)
{
int result = Math.Clamp(value, inputMinimum, inputMaximum) * weight;
result = rightShift > 0 ? (result + (1 << (rightShift - 1))) >> rightShift : result << -rightShift;
return Math.Clamp(result, outputMinimum, outputMaximum);
}
}

272
src/ImageSharp/Formats/Heif/Hevc/HevcScalingList.cs

@ -14,9 +14,60 @@ internal sealed class HevcScalingList
private const int MatrixCount = 6;
/// <summary>
/// The flat default matrix used by four-by-four transforms.
/// The number of decoded scaling coefficients stored for every transform-size category.
/// </summary>
private static readonly byte[] Default4x4 =
private const int CompactCoefficientCount = (16 * MatrixCount) + (64 * MatrixCount * 3);
/// <summary>
/// The number of separately coded DC coefficients.
/// </summary>
private const int DcCoefficientCount = 4 * MatrixCount;
/// <summary>
/// The first separately coded DC coefficient in the contiguous coefficient store.
/// </summary>
private const int DcCoefficientOffset = CompactCoefficientCount;
/// <summary>
/// The first transform-sized matrix in the contiguous coefficient store.
/// </summary>
private const int ExpandedCoefficientOffset = DcCoefficientOffset + DcCoefficientCount;
/// <summary>
/// The number of transform-sized coefficients stored across every size and matrix identifier.
/// </summary>
private const int ExpandedCoefficientCount = MatrixCount * ((4 * 4) + (8 * 8) + (16 * 16) + (32 * 32));
/// <summary>
/// The compact syntax matrices, separately coded DC values, and transform-sized matrices.
/// </summary>
private readonly byte[] coefficients = new byte[ExpandedCoefficientOffset + ExpandedCoefficientCount];
/// <summary>
/// Initializes a new instance of the <see cref="HevcScalingList"/> class with the normative default matrices.
/// </summary>
public HevcScalingList()
{
for (int sizeId = 0; sizeId < 4; sizeId++)
{
for (int matrixId = 0; matrixId < MatrixCount; matrixId++)
{
ReadOnlySpan<byte> source = sizeId == 0
? Default4x4
: matrixId < 3 ? DefaultIntra8x8 : DefaultInter8x8;
source.CopyTo(this.GetWritableMatrix(sizeId, matrixId));
this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = 16;
}
}
this.ExpandMatrices();
}
/// <summary>
/// Gets the flat default matrix used by four-by-four transforms.
/// </summary>
private static ReadOnlySpan<byte> Default4x4 =>
[
16, 16, 16, 16,
16, 16, 16, 16,
@ -25,9 +76,9 @@ internal sealed class HevcScalingList
];
/// <summary>
/// The default intra-predicted matrix used by transforms of eight-by-eight and larger.
/// Gets the default intra-predicted matrix used by transforms of eight-by-eight and larger.
/// </summary>
private static readonly byte[] DefaultIntra8x8 =
private static ReadOnlySpan<byte> DefaultIntra8x8 =>
[
16, 16, 16, 16, 17, 18, 21, 24,
16, 16, 16, 16, 17, 19, 22, 25,
@ -40,9 +91,9 @@ internal sealed class HevcScalingList
];
/// <summary>
/// The default inter-predicted matrix used by transforms of eight-by-eight and larger.
/// Gets the default inter-predicted matrix used by transforms of eight-by-eight and larger.
/// </summary>
private static readonly byte[] DefaultInter8x8 =
private static ReadOnlySpan<byte> DefaultInter8x8 =>
[
16, 16, 16, 16, 17, 18, 20, 24,
16, 16, 16, 17, 18, 20, 24, 25,
@ -55,46 +106,23 @@ internal sealed class HevcScalingList
];
/// <summary>
/// The diagonal coefficient order for four-by-four matrices.
/// </summary>
private static readonly byte[] DiagonalScan4x4 = CreateDiagonalScan(4);
/// <summary>
/// The diagonal coefficient order for matrices of eight-by-eight and larger.
/// Gets the diagonal coefficient order for four-by-four matrices.
/// </summary>
private static readonly byte[] DiagonalScan8x8 = CreateDiagonalScan(8);
/// <summary>
/// The decoded matrices, indexed by transform-size category and matrix identifier.
/// </summary>
private readonly byte[][] matrices = new byte[4 * MatrixCount][];
private static ReadOnlySpan<byte> DiagonalScan4x4 =>
[
0, 4, 1, 8, 5, 2, 12, 9, 6, 3, 13, 10, 7, 14, 11, 15
];
/// <summary>
/// The DC coefficients for sixteen-by-sixteen and thirty-two-by-thirty-two matrices.
/// Gets the diagonal coefficient order for matrices of eight-by-eight and larger.
/// </summary>
private readonly byte[] dcCoefficients = new byte[4 * MatrixCount];
/// <summary>
/// Initializes a new instance of the <see cref="HevcScalingList"/> class with the normative default matrices.
/// </summary>
public HevcScalingList()
{
for (int sizeId = 0; sizeId < 4; sizeId++)
{
int coefficientCount = sizeId == 0 ? 16 : 64;
for (int matrixId = 0; matrixId < MatrixCount; matrixId++)
{
byte[] matrix = new byte[coefficientCount];
ReadOnlySpan<byte> source = sizeId == 0
? Default4x4
: matrixId < 3 ? DefaultIntra8x8 : DefaultInter8x8;
source.CopyTo(matrix);
this.matrices[GetIndex(sizeId, matrixId)] = matrix;
this.dcCoefficients[GetIndex(sizeId, matrixId)] = 16;
}
}
}
private static ReadOnlySpan<byte> DiagonalScan8x8 =>
[
0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 32, 25, 18, 11, 4, 40,
33, 26, 19, 12, 5, 48, 41, 34, 27, 20, 13, 6, 56, 49, 42, 35,
28, 21, 14, 7, 57, 50, 43, 36, 29, 22, 15, 58, 51, 44, 37, 30,
23, 59, 52, 45, 38, 31, 60, 53, 46, 39, 61, 54, 47, 62, 55, 63
];
/// <summary>
/// Reads a complete scaling-list-data structure.
@ -110,7 +138,6 @@ internal sealed class HevcScalingList
int matrixStep = sizeId == 3 ? 3 : 1;
for (int matrixId = 0; matrixId < MatrixCount; matrixId += matrixStep)
{
int destinationIndex = GetIndex(sizeId, matrixId);
bool predictionMode = reader.ReadFlag();
if (!predictionMode)
{
@ -133,12 +160,12 @@ internal sealed class HevcScalingList
int referenceMatrixId = matrixId - (int)matrixIdDelta;
if (referenceMatrixId != matrixId)
{
int referenceIndex = GetIndex(sizeId, referenceMatrixId);
// Span copying uses ImageSharp's runtime-optimized memory path and preserves one scalar
// behavior model for these small, infrequently parsed coefficient tables.
scalingList.matrices[referenceIndex].CopyTo(scalingList.matrices[destinationIndex], 0);
scalingList.dcCoefficients[destinationIndex] = scalingList.dcCoefficients[referenceIndex];
scalingList.GetMatrix(sizeId, referenceMatrixId).CopyTo(scalingList.GetWritableMatrix(sizeId, matrixId));
byte dcCoefficient = scalingList.coefficients[GetDcCoefficientOffset(sizeId, referenceMatrixId)];
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = dcCoefficient;
}
continue;
@ -148,11 +175,11 @@ internal sealed class HevcScalingList
if (sizeId > 1)
{
nextCoefficient = (int)(((long)reader.ReadSignedExpGolomb() + 8) & 255);
scalingList.dcCoefficients[destinationIndex] = (byte)(nextCoefficient & 255);
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = (byte)(nextCoefficient & 255);
}
byte[] scan = sizeId == 0 ? DiagonalScan4x4 : DiagonalScan8x8;
byte[] matrix = scalingList.matrices[destinationIndex];
ReadOnlySpan<byte> scan = sizeId == 0 ? DiagonalScan4x4 : DiagonalScan8x8;
Span<byte> matrix = scalingList.GetWritableMatrix(sizeId, matrixId);
for (int coefficient = 0; coefficient < matrix.Length; coefficient++)
{
nextCoefficient = (int)(((long)nextCoefficient + reader.ReadSignedExpGolomb()) & 255);
@ -170,14 +197,13 @@ internal sealed class HevcScalingList
continue;
}
int destinationIndex = GetIndex(sizeId, matrixId);
int referenceIndex = GetIndex(sizeId - 1, matrixId);
scalingList.matrices[referenceIndex].CopyTo(scalingList.matrices[destinationIndex], 0);
scalingList.dcCoefficients[destinationIndex] = scalingList.dcCoefficients[referenceIndex];
scalingList.GetMatrix(sizeId - 1, matrixId).CopyTo(scalingList.GetWritableMatrix(sizeId, matrixId));
scalingList.coefficients[GetDcCoefficientOffset(sizeId, matrixId)] = scalingList.coefficients[GetDcCoefficientOffset(sizeId - 1, matrixId)];
}
}
}
scalingList.ExpandMatrices();
return scalingList;
}
@ -191,60 +217,136 @@ internal sealed class HevcScalingList
{
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId));
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId));
return this.matrices[GetIndex(sizeId, matrixId)];
int coefficientCount = GetCompactMatrixLength(sizeId);
return this.coefficients.AsSpan(GetCompactMatrixOffset(sizeId, matrixId), coefficientCount);
}
/// <summary>
/// Gets the DC scaling coefficient for a large transform matrix.
/// Gets a scaling matrix expanded to its transform dimensions.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="sizeId">The transform-size category from zero for 4x4 through three for 32x32.</param>
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
/// <returns>The decoded DC coefficient.</returns>
public byte GetDcCoefficient(int sizeId, int matrixId)
/// <returns>The transform-sized scaling coefficients in raster order.</returns>
public ReadOnlySpan<byte> GetExpandedMatrix(int sizeId, int matrixId)
{
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId));
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId));
return this.dcCoefficients[GetIndex(sizeId, matrixId)];
int coefficientCount = GetExpandedMatrixLength(sizeId);
return this.coefficients.AsSpan(GetExpandedMatrixOffset(sizeId, matrixId), coefficientCount);
}
/// <summary>
/// Gets the flattened storage index for a size and matrix identifier.
/// Gets the DC scaling coefficient for a large transform matrix.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The flattened storage index.</returns>
private static int GetIndex(int sizeId, int matrixId) => (sizeId * MatrixCount) + matrixId;
/// <param name="matrixId">The prediction and color-component matrix identifier.</param>
/// <returns>The decoded DC coefficient.</returns>
public byte GetDcCoefficient(int sizeId, int matrixId)
{
DebugGuard.MustBeBetweenOrEqualTo(sizeId, 0, 3, nameof(sizeId));
DebugGuard.MustBeBetweenOrEqualTo(matrixId, 0, MatrixCount - 1, nameof(matrixId));
return this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)];
}
/// <summary>
/// Creates the HEVC up-right diagonal scan for a square coefficient block.
/// Expands every syntax matrix once so inverse quantization can consume consecutive weights without coordinate division.
/// </summary>
/// <param name="size">The coefficient block width and height.</param>
/// <returns>The raster indices in coded order.</returns>
private static byte[] CreateDiagonalScan(int size)
private void ExpandMatrices()
{
byte[] scan = new byte[size * size];
int row = 0;
int column = 0;
for (int position = 0; position < scan.Length; position++)
for (int sizeId = 0; sizeId < 4; sizeId++)
{
scan[position] = (byte)((row * size) + column);
if (column == size - 1 || row == 0)
int size = 1 << (sizeId + 2);
int ratio = Math.Max(1, size >> 3);
int sourceSide = Math.Min(size, 8);
for (int matrixId = 0; matrixId < MatrixCount; matrixId++)
{
row += column + 1;
column = 0;
if (row >= size)
ReadOnlySpan<byte> source = this.GetMatrix(sizeId, matrixId);
Span<byte> destination = this.coefficients.AsSpan(GetExpandedMatrixOffset(sizeId, matrixId), size * size);
for (int y = 0; y < size; y++)
{
column += row - (size - 1);
row = size - 1;
int sourceRowOffset = (y / ratio) * sourceSide;
int destinationRowOffset = y * size;
for (int x = 0; x < size; x++)
{
destination[destinationRowOffset + x] = source[sourceRowOffset + (x / ratio)];
}
}
if (sizeId > 1)
{
// Sixteen- and thirty-two-point matrices code their DC weight separately from the 8x8 body.
destination[0] = this.coefficients[GetDcCoefficientOffset(sizeId, matrixId)];
}
}
else
{
column++;
row--;
}
}
}
/// <summary>
/// Gets a writable compact syntax matrix.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The writable compact matrix.</returns>
private Span<byte> GetWritableMatrix(int sizeId, int matrixId)
=> this.coefficients.AsSpan(GetCompactMatrixOffset(sizeId, matrixId), GetCompactMatrixLength(sizeId));
/// <summary>
/// Gets the number of coefficients coded for one syntax matrix.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <returns>The compact coefficient count.</returns>
private static int GetCompactMatrixLength(int sizeId) => sizeId == 0 ? 16 : 64;
/// <summary>
/// Gets the number of coefficients in one transform-sized matrix.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <returns>The expanded coefficient count.</returns>
private static int GetExpandedMatrixLength(int sizeId) => 1 << ((sizeId + 2) * 2);
return scan;
/// <summary>
/// Gets the compact-matrix offset for a size and matrix identifier.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The compact-matrix offset.</returns>
private static int GetCompactMatrixOffset(int sizeId, int matrixId)
{
int sizeOffset = sizeId switch
{
0 => 0,
1 => 16 * MatrixCount,
2 => (16 * MatrixCount) + (64 * MatrixCount),
_ => (16 * MatrixCount) + (64 * MatrixCount * 2),
};
return sizeOffset + (matrixId * GetCompactMatrixLength(sizeId));
}
/// <summary>
/// Gets the expanded-matrix offset for a size and matrix identifier.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The expanded-matrix offset.</returns>
private static int GetExpandedMatrixOffset(int sizeId, int matrixId)
{
int sizeOffset = sizeId switch
{
0 => 0,
1 => 16 * MatrixCount,
2 => (16 * MatrixCount) + (64 * MatrixCount),
_ => (16 * MatrixCount) + (64 * MatrixCount) + (256 * MatrixCount),
};
return ExpandedCoefficientOffset + sizeOffset + (matrixId * GetExpandedMatrixLength(sizeId));
}
/// <summary>
/// Gets the separately coded DC-coefficient offset for a size and matrix identifier.
/// </summary>
/// <param name="sizeId">The transform-size category.</param>
/// <param name="matrixId">The matrix identifier.</param>
/// <returns>The DC-coefficient offset.</returns>
private static int GetDcCoefficientOffset(int sizeId, int matrixId) => DcCoefficientOffset + (sizeId * MatrixCount) + matrixId;
}

105
tests/ImageSharp.Benchmarks/Codecs/Heif/HevcInverseQuantizationBenchmarks.cs

@ -0,0 +1,105 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Heif.Hevc;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif;
/// <summary>
/// Measures complete coded-frame traversal through HEVC inverse quantization.
/// </summary>
[MemoryDiagnoser(displayGenColumns: false)]
public class HevcInverseQuantizationBenchmarks
{
/// <summary>
/// The coded frame width, which is an exact multiple of the maximum transform-block side.
/// </summary>
private const int Width = 1920;
/// <summary>
/// The coded frame height including the final padded coding-tree row for a 1080-line presentation.
/// </summary>
private const int Height = 1088;
/// <summary>
/// The base-two logarithm of the benchmark transform-block side.
/// </summary>
private const int BlockLog2 = 5;
/// <summary>
/// The transform-block side in samples.
/// </summary>
private const int BlockSize = 1 << BlockLog2;
/// <summary>
/// The deterministic quantized coefficients reused by each benchmark block.
/// </summary>
private readonly int[] quantized = new int[BlockSize * BlockSize];
/// <summary>
/// The reusable dequantized coefficient block.
/// </summary>
private readonly int[] destination = new int[BlockSize * BlockSize];
/// <summary>
/// The effective default scaling matrices.
/// </summary>
private readonly HevcScalingList scalingList = new();
/// <summary>
/// Populates a dense, deterministic twelve-bit transform workload outside the measured frame traversal.
/// </summary>
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < this.quantized.Length; i++)
{
this.quantized[i] = (((i * 7919) + 257) & 131071) - 65536;
}
}
/// <summary>
/// Measures frame-wide inverse quantization using one uniform scale.
/// </summary>
/// <returns>The final coefficient, keeping the block output observable.</returns>
[Benchmark(Baseline = true)]
public int DequantizeFlatFrame() => this.DequantizeFrame(false);
/// <summary>
/// Measures frame-wide inverse quantization using an expanded thirty-two-by-thirty-two scaling matrix.
/// </summary>
/// <returns>The final coefficient, keeping the block output observable.</returns>
[Benchmark]
public int DequantizeScalingListFrame() => this.DequantizeFrame(true);
/// <summary>
/// Dequantizes every maximum-size transform block in the coded benchmark frame.
/// </summary>
/// <param name="scalingListEnabled">Whether the default intra-luma scaling matrix applies.</param>
/// <returns>The final dequantized coefficient.</returns>
private int DequantizeFrame(bool scalingListEnabled)
{
for (int y = 0; y < Height; y += BlockSize)
{
for (int x = 0; x < Width; x += BlockSize)
{
HevcInverseQuantizer.Dequantize(
this.quantized,
this.destination,
BlockLog2,
12,
18,
75,
scalingListEnabled,
this.scalingList,
HevcPlane.Y,
true,
false,
true);
}
}
return this.destination[^1];
}
}

187
tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcInverseQuantizerTests.cs

@ -0,0 +1,187 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats.Heif.Hevc;
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc;
/// <summary>
/// Verifies HEVC inverse quantization, scaling-list expansion, clipping, and SIMD behavior.
/// </summary>
[Trait("Format", "Heic")]
public class HevcInverseQuantizerTests
{
/// <summary>
/// Verifies fixed flat-scale results including signed rounding and both transform-range limits.
/// </summary>
[Fact]
public void FlatScaleMatchesFixedResults()
{
int[] quantized = [1, -1, 2, -2, int.MaxValue, int.MinValue, 3, -3, 0, 0, 0, 0, 0, 0, 0, 0];
int[] actual = new int[quantized.Length];
HevcScalingList scalingList = new();
HevcInverseQuantizer.Dequantize(quantized, actual, 2, 8, 15, 0, false, scalingList, HevcPlane.Y, true, false, false);
int[] expected = [20, -20, 40, -40, 32767, -32768, 60, -60, 0, 0, 0, 0, 0, 0, 0, 0];
Assert.True(expected.AsSpan().SequenceEqual(actual));
}
/// <summary>
/// Verifies fixed nonuniform values from the default eight-by-eight intra-luma scaling matrix.
/// </summary>
[Fact]
public void DefaultEightByEightScalingMatrixMatchesFixedResults()
{
int[] quantized = new int[64];
quantized.AsSpan().Fill(1);
int[] actual = new int[quantized.Length];
HevcScalingList scalingList = new();
HevcInverseQuantizer.Dequantize(quantized, actual, 3, 8, 15, 0, true, scalingList, HevcPlane.Y, true, false, false);
Assert.Equal(10, actual[0]);
Assert.Equal(11, actual[4]);
Assert.Equal(13, actual[6]);
Assert.Equal(72, actual[63]);
}
/// <summary>
/// Compares SIMD inverse quantization with a scalar oracle across transform sizes, precisions, scaling modes, and shift directions.
/// </summary>
/// <param name="log2Size">The base-two logarithm of the tested transform-block side.</param>
/// <param name="bitDepth">The reconstructed component precision.</param>
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
/// <param name="quantizationParameter">The effective component quantization parameter.</param>
/// <param name="scalingListEnabled">Whether scaling lists are enabled.</param>
/// <param name="transformSkip">Whether the transform is skipped.</param>
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended.</param>
[Theory]
[InlineData(2, 8, 15, 0, false, false, false)]
[InlineData(2, 8, 15, 27, true, false, false)]
[InlineData(2, 10, 15, 39, true, true, false)]
[InlineData(2, 12, 18, 51, true, true, true)]
[InlineData(3, 10, 15, 45, true, false, false)]
[InlineData(3, 10, 15, 45, true, true, false)]
[InlineData(4, 12, 18, 63, true, false, false)]
[InlineData(5, 12, 18, 75, true, false, false)]
[InlineData(5, 12, 15, 51, false, false, false)]
public void DequantizeMatchesScalarOracle(
int log2Size,
int bitDepth,
int maxTransformDynamicRange,
int quantizationParameter,
bool scalingListEnabled,
bool transformSkip,
bool extendedPrecisionProcessingEnabled)
{
int size = 1 << log2Size;
int[] quantized = new int[size * size];
for (int i = 0; i < quantized.Length; i++)
{
quantized[i] = (((i * 7919) + (size * 257)) & 131071) - 65536;
}
HevcScalingList scalingList = new();
int[] expected = new int[quantized.Length];
int[] actual = new int[quantized.Length];
DequantizeScalar(
quantized,
expected,
log2Size,
bitDepth,
maxTransformDynamicRange,
quantizationParameter,
scalingListEnabled,
scalingList,
HevcPlane.Y,
true,
transformSkip,
extendedPrecisionProcessingEnabled);
HevcInverseQuantizer.Dequantize(
quantized,
actual,
log2Size,
bitDepth,
maxTransformDynamicRange,
quantizationParameter,
scalingListEnabled,
scalingList,
HevcPlane.Y,
true,
transformSkip,
extendedPrecisionProcessingEnabled);
Assert.True(expected.AsSpan().SequenceEqual(actual), $"The {size}x{size} inverse quantizer did not match the scalar HEVC oracle.");
}
/// <summary>
/// Applies the HEVC inverse-quantization equations directly for one complete transform block.
/// </summary>
/// <param name="source">The quantized coefficients.</param>
/// <param name="destination">The dequantized coefficients.</param>
/// <param name="log2Size">The base-two logarithm of the transform-block side.</param>
/// <param name="bitDepth">The reconstructed component precision.</param>
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
/// <param name="quantizationParameter">The effective component quantization parameter.</param>
/// <param name="scalingListEnabled">Whether the sequence enables scaling matrices.</param>
/// <param name="scalingList">The effective picture scaling matrices.</param>
/// <param name="plane">The reconstructed color plane.</param>
/// <param name="isIntraPredicted">Whether the transform block belongs to an intra-predicted coding unit.</param>
/// <param name="transformSkip">Whether the transform is skipped.</param>
/// <param name="extendedPrecisionProcessingEnabled">Whether transform-skip precision is extended.</param>
private static void DequantizeScalar(
ReadOnlySpan<int> source,
Span<int> destination,
int log2Size,
int bitDepth,
int maxTransformDynamicRange,
int quantizationParameter,
bool scalingListEnabled,
HevcScalingList scalingList,
HevcPlane plane,
bool isIntraPredicted,
bool transformSkip,
bool extendedPrecisionProcessingEnabled)
{
ReadOnlySpan<byte> inverseScales = [40, 45, 51, 57, 64, 72];
int size = 1 << log2Size;
int transformShift = maxTransformDynamicRange - bitDepth - log2Size;
if (transformSkip && extendedPrecisionProcessingEnabled)
{
transformShift = Math.Max(0, transformShift);
}
int inverseScale = inverseScales[quantizationParameter % 6];
bool useScalingList = scalingListEnabled && (!transformSkip || log2Size == 2);
int rightShift = 6 - (transformShift + (quantizationParameter / 6)) + (useScalingList ? 4 : 0);
int targetInputBitDepth = Math.Min(maxTransformDynamicRange + 1, 32 + rightShift - (useScalingList ? 15 : 7));
int inputMinimum = -(1 << (targetInputBitDepth - 1));
int inputMaximum = (1 << (targetInputBitDepth - 1)) - 1;
int outputMinimum = -(1 << maxTransformDynamicRange);
int outputMaximum = (1 << maxTransformDynamicRange) - 1;
int sizeId = log2Size - 2;
int matrixId = (isIntraPredicted ? 0 : 3) + (int)plane;
ReadOnlySpan<byte> matrix = scalingList.GetMatrix(sizeId, matrixId);
int ratio = Math.Max(1, size >> 3);
int matrixSide = Math.Min(size, 8);
byte dcCoefficient = scalingList.GetDcCoefficient(sizeId, matrixId);
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
int coefficient = useScalingList
? ratio > 1 && x == 0 && y == 0
? dcCoefficient
: matrix[((y / ratio) * matrixSide) + (x / ratio)]
: 1;
int value = Math.Clamp(source[(y * size) + x], inputMinimum, inputMaximum) * inverseScale * coefficient;
value = rightShift > 0 ? (value + (1 << (rightShift - 1))) >> rightShift : value << -rightShift;
destination[(y * size) + x] = Math.Clamp(value, outputMinimum, outputMaximum);
}
}
}
}
Loading…
Cancel
Save