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