mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 884 additions and 86 deletions
@ -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); |
|||
} |
|||
} |
|||
@ -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]; |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue