mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 1294 additions and 6 deletions
@ -0,0 +1,523 @@ |
|||
// 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.Formats.Heif.Av1.Transform; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Calculates the disjoint odd-frequency groups that seed the HEVC partial-butterfly reconstruction.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="groups">The destination group rows.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
private static void PopulateButterflyGroups<TOperator>(ReadOnlySpan<int> source, Span<int> groups, int lineCount) |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
int size = TOperator.Size; |
|||
int groupOffset = 0; |
|||
for (int frequencyStep = 2; frequencyStep < size; frequencyStep <<= 1) |
|||
{ |
|||
int outputCount = size / frequencyStep; |
|||
int firstFrequency = frequencyStep >> 1; |
|||
for (int position = 0; position < outputCount; position++) |
|||
{ |
|||
PopulateButterflyGroupRow<TOperator>( |
|||
source, |
|||
groups.Slice((groupOffset + position) * lineCount, lineCount), |
|||
lineCount, |
|||
firstFrequency, |
|||
frequencyStep, |
|||
position); |
|||
} |
|||
|
|||
groupOffset += outputCount; |
|||
} |
|||
|
|||
// The deepest even group contains the DC term and the transform's Nyquist-frequency term. It remains a
|
|||
// two-element group for every supported DCT size and closes the recursive butterfly hierarchy.
|
|||
for (int position = 0; position < 2; position++) |
|||
{ |
|||
PopulateButterflyGroupRow<TOperator>( |
|||
source, |
|||
groups.Slice((groupOffset + position) * lineCount, lineCount), |
|||
lineCount, |
|||
0, |
|||
size >> 1, |
|||
position); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one partial-butterfly group row across all independent lines.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="source">The complete frequency-row input.</param>
|
|||
/// <param name="destination">The destination group row.</param>
|
|||
/// <param name="lineCount">The number of independent lines.</param>
|
|||
/// <param name="firstFrequency">The first frequency included by the group.</param>
|
|||
/// <param name="frequencyStep">The distance between included frequencies.</param>
|
|||
/// <param name="position">The group-relative spatial coordinate.</param>
|
|||
private static void PopulateButterflyGroupRow<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
int lineCount, |
|||
int firstFrequency, |
|||
int frequencyStep, |
|||
int position) |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int x = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> sum = Vector512<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector512.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> sum = Vector256<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector256.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> sum = Vector128<int>.Zero; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector128.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
sum.StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
for (; x < lineCount; x++) |
|||
{ |
|||
int sum = 0; |
|||
for (int frequency = firstFrequency; frequency < TOperator.Size; frequency += frequencyStep) |
|||
{ |
|||
sum += source[(frequency * lineCount) + x] * TOperator.GetCoefficient(frequency, position); |
|||
} |
|||
|
|||
destination[x] = sum; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Expands the disjoint partial-butterfly groups into spatial rows.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-DCT operator.</typeparam>
|
|||
/// <param name="initial">The buffer containing every disjoint group.</param>
|
|||
/// <param name="alternate">The alternate expansion buffer.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied at the final hierarchy level.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <returns>The buffer containing the completed spatial rows.</returns>
|
|||
private static Span<int> CombineButterflyGroups<TOperator>( |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
int size = TOperator.Size; |
|||
int combinedSize = 2; |
|||
int combinedStart = size - combinedSize; |
|||
bool currentIsInitial = true; |
|||
while (combinedSize < size) |
|||
{ |
|||
int oddStart = combinedStart - combinedSize; |
|||
bool finalLevel = (combinedSize << 1) == size; |
|||
ReadOnlySpan<int> even = currentIsInitial ? initial : alternate; |
|||
Span<int> destination = currentIsInitial ? alternate : initial; |
|||
|
|||
for (int position = 0; position < combinedSize; position++) |
|||
{ |
|||
ReadOnlySpan<int> evenRow = even.Slice((combinedStart + position) * lineCount, lineCount); |
|||
ReadOnlySpan<int> oddRow = initial.Slice((oddStart + position) * lineCount, lineCount); |
|||
Span<int> positiveRow = destination.Slice((oddStart + position) * lineCount, lineCount); |
|||
Span<int> negativeRow = destination.Slice((oddStart + (2 * combinedSize) - 1 - position) * lineCount, lineCount); |
|||
CombineButterflyRows(evenRow, oddRow, positiveRow, negativeRow, finalLevel, shift, minimum, maximum); |
|||
} |
|||
|
|||
combinedStart = oddStart; |
|||
combinedSize <<= 1; |
|||
currentIsInitial = !currentIsInitial; |
|||
} |
|||
|
|||
return currentIsInitial ? initial : alternate; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Combines one symmetric pair of partial-butterfly rows.
|
|||
/// </summary>
|
|||
/// <param name="even">The even-frequency contribution.</param>
|
|||
/// <param name="odd">The odd-frequency contribution.</param>
|
|||
/// <param name="positive">The destination receiving the added contribution.</param>
|
|||
/// <param name="negative">The destination receiving the subtracted contribution.</param>
|
|||
/// <param name="roundAndClip">Whether this is the final hierarchy level.</param>
|
|||
/// <param name="shift">The rounded right shift applied at the final hierarchy level.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
private static void CombineButterflyRows( |
|||
ReadOnlySpan<int> even, |
|||
ReadOnlySpan<int> odd, |
|||
Span<int> positive, |
|||
Span<int> negative, |
|||
bool roundAndClip, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
{ |
|||
ref int evenBase = ref MemoryMarshal.GetReference(even); |
|||
ref int oddBase = ref MemoryMarshal.GetReference(odd); |
|||
ref int positiveBase = ref MemoryMarshal.GetReference(positive); |
|||
ref int negativeBase = ref MemoryMarshal.GetReference(negative); |
|||
int x = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> evenValues = Vector512.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector512<int> oddValues = Vector512.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector512<int> added = evenValues + oddValues; |
|||
Vector512<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> evenValues = Vector256.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector256<int> oddValues = Vector256.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector256<int> added = evenValues + oddValues; |
|||
Vector256<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = even.Length - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> evenValues = Vector128.LoadUnsafe(ref evenBase, (nuint)x); |
|||
Vector128<int> oddValues = Vector128.LoadUnsafe(ref oddBase, (nuint)x); |
|||
Vector128<int> added = evenValues + oddValues; |
|||
Vector128<int> subtracted = evenValues - oddValues; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
added.StoreUnsafe(ref positiveBase, (nuint)x); |
|||
subtracted.StoreUnsafe(ref negativeBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
for (; x < even.Length; x++) |
|||
{ |
|||
int added = even[x] + odd[x]; |
|||
int subtracted = even[x] - odd[x]; |
|||
if (roundAndClip) |
|||
{ |
|||
added = RoundShiftAndClamp(added, shift, minimum, maximum); |
|||
subtracted = RoundShiftAndClamp(subtracted, shift, minimum, maximum); |
|||
} |
|||
|
|||
positive[x] = added; |
|||
negative[x] = subtracted; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the four-point inverse-DST matrix across all independent lines.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected dense transform operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="destination">The spatial rows followed by contiguous independent lines.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to each result.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
private static void TransformDense<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> destination, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
for (int position = 0; position < TOperator.Size; position++) |
|||
{ |
|||
int x = 0; |
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector512<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> sum = Vector512<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector512<int> values = Vector512.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector512.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> sum = Vector256<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector256<int> values = Vector256.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector256.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = lineCount - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> sum = Vector128<int>.Zero; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
Vector128<int> values = Vector128.LoadUnsafe(ref sourceBase, (nuint)((frequency * lineCount) + x)); |
|||
sum += values * Vector128.Create(TOperator.GetCoefficient(frequency, position)); |
|||
} |
|||
|
|||
RoundShiftAndClamp(sum, shift, minimum, maximum).StoreUnsafe(ref destinationBase, (nuint)((position * lineCount) + x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < lineCount; x++) |
|||
{ |
|||
int sum = 0; |
|||
for (int frequency = 0; frequency < TOperator.Size; frequency++) |
|||
{ |
|||
sum += source[(frequency * lineCount) + x] * TOperator.GetCoefficient(frequency, position); |
|||
} |
|||
|
|||
destination[(position * lineCount) + x] = RoundShiftAndClamp(sum, shift, minimum, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes one rectangular transform block into a separate full-block buffer.
|
|||
/// </summary>
|
|||
/// <param name="source">The source block in raster order.</param>
|
|||
/// <param name="destination">The transposed destination block.</param>
|
|||
/// <param name="sourceHeight">The source row count.</param>
|
|||
/// <param name="sourceWidth">The source column count.</param>
|
|||
private static void Transpose(ReadOnlySpan<int> source, Span<int> destination, int sourceHeight, int sourceWidth) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
ref int sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref int destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
for (int y = 0; y < sourceHeight; y += 4) |
|||
{ |
|||
for (int x = 0; x < sourceWidth; x += 4) |
|||
{ |
|||
Vector128<int> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)((y * sourceWidth) + x)); |
|||
Vector128<int> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceWidth) + x)); |
|||
Vector128<int> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceWidth) + x)); |
|||
Vector128<int> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceWidth) + x)); |
|||
Av1Transform2dOperations.Transpose(ref row0, ref row1, ref row2, ref row3); |
|||
row0.StoreUnsafe(ref destinationBase, (nuint)((x * sourceHeight) + y)); |
|||
row1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * sourceHeight) + y)); |
|||
row2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * sourceHeight) + y)); |
|||
row3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * sourceHeight) + y)); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int y = 0; y < sourceHeight; y++) |
|||
{ |
|||
for (int x = 0; x < sourceWidth; x++) |
|||
{ |
|||
destination[(x * sourceHeight) + y] = source[(y * sourceWidth) + x]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a complete signed residual block to the predicted samples and clips to the component precision.
|
|||
/// </summary>
|
|||
/// <param name="residual">The signed residual block in raster order.</param>
|
|||
/// <param name="destination">The predicted samples beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
private static void AddResidual(ReadOnlySpan<int> residual, Span<ushort> destination, int destinationStride, int width, int height, int bitDepth) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
ReadOnlySpan<int> residualRow = residual.Slice(y * width, width); |
|||
Span<ushort> destinationRow = destination.Slice(y * destinationStride, width); |
|||
ref int residualBase = ref MemoryMarshal.GetReference(residualRow); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destinationRow); |
|||
int x = 0; |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector256<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<int>.Count) |
|||
{ |
|||
Vector128<ushort> predicted16 = Vector128.LoadUnsafe(ref destinationBase, (nuint)x); |
|||
Vector256<int> predicted = Vector256.Create(Vector128.WidenLower(predicted16), Vector128.WidenUpper(predicted16)).AsInt32(); |
|||
Vector256<int> reconstructed = Vector256.Clamp( |
|||
predicted + Vector256.LoadUnsafe(ref residualBase, (nuint)x), |
|||
Vector256<int>.Zero, |
|||
Vector256.Create(maximum)); |
|||
|
|||
Vector128.Narrow(reconstructed.GetLower().AsUInt32(), reconstructed.GetUpper().AsUInt32()).StoreUnsafe(ref destinationBase, (nuint)x); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = width - Vector128<int>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<int>.Count) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref Unsafe.Add(ref destinationBase, x))); |
|||
Vector128<int> predicted = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsUInt16()).AsInt32(); |
|||
Vector128<int> reconstructed = Vector128.Clamp( |
|||
predicted + Vector128.LoadUnsafe(ref residualBase, (nuint)x), |
|||
Vector128<int>.Zero, |
|||
Vector128.Create(maximum)); |
|||
|
|||
Vector128<ushort> narrowed = Vector128.Narrow(reconstructed.AsUInt32(), Vector128<uint>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<ushort, byte>(ref Unsafe.Add(ref destinationBase, x)), narrowed.AsUInt64().ToScalar()); |
|||
} |
|||
} |
|||
|
|||
for (; x < width; x++) |
|||
{ |
|||
destinationRow[x] = (ushort)Math.Clamp(destinationRow[x] + residualRow[x], 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 512-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<int> RoundShiftAndClamp(Vector512<int> value, int shift, int minimum, int maximum) |
|||
=> Vector512.Clamp((value + Vector512.Create(1 << (shift - 1))) >> shift, Vector512.Create(minimum), Vector512.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 256-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<int> RoundShiftAndClamp(Vector256<int> value, int shift, int minimum, int maximum) |
|||
=> Vector256.Clamp((value + Vector256.Create(1 << (shift - 1))) >> shift, Vector256.Create(minimum), Vector256.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform values.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped values.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<int> RoundShiftAndClamp(Vector128<int> value, int shift, int minimum, int maximum) |
|||
=> Vector128.Clamp((value + Vector128.Create(1 << (shift - 1))) >> shift, Vector128.Create(minimum), Vector128.Create(maximum)); |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC's rounded right shift and inclusive clipping to one scalar value.
|
|||
/// </summary>
|
|||
/// <param name="value">The unnormalized transform value.</param>
|
|||
/// <param name="shift">The right-shift count.</param>
|
|||
/// <param name="minimum">The inclusive result minimum.</param>
|
|||
/// <param name="maximum">The inclusive result maximum.</param>
|
|||
/// <returns>The normalized and clipped value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RoundShiftAndClamp(int value, int shift, int minimum, int maximum) |
|||
=> Math.Clamp((value + (1 << (shift - 1))) >> shift, minimum, maximum); |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the common HEVC inverse-DCT magnitudes ordered on the pi-over-sixty-four angle grid.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<sbyte> DiscreteCosineMagnitudes => |
|||
[ |
|||
90, 90, 90, 90, 89, 88, 87, 85, 83, 82, 80, 78, 75, 73, 70, 67, 64, |
|||
61, 57, 54, 50, 46, 43, 38, 36, 31, 25, 22, 18, 13, 9, 4, 0 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the four-point HEVC inverse-DST matrix in frequency-major order.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<sbyte> DiscreteSine4Coefficients => |
|||
[ |
|||
29, 55, 74, 84, |
|||
74, 74, 0, -74, |
|||
84, -29, -74, 55, |
|||
55, -84, 74, -29 |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one normative inverse-DCT coefficient from the common HEVC angle grid.
|
|||
/// </summary>
|
|||
/// <param name="size">The transform side.</param>
|
|||
/// <param name="frequency">The frequency-domain coordinate.</param>
|
|||
/// <param name="position">The spatial-domain coordinate.</param>
|
|||
/// <returns>The signed transform coefficient.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int GetDiscreteCosineCoefficient(int size, int frequency, int position) |
|||
{ |
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
int angle = ((2 * position) + 1) * frequency * (32 / size); |
|||
angle &= 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
// Angles in the second quadrant reuse the first-quadrant magnitude with a negative sign. Keeping the
|
|||
// normative integer magnitudes in one compile-time span avoids runtime trigonometry and duplicate matrices.
|
|||
if (angle > 32) |
|||
{ |
|||
return -DiscreteCosineMagnitudes[64 - angle]; |
|||
} |
|||
|
|||
return DiscreteCosineMagnitudes[angle]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the four-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine4Operator : IHevcInverseTransformOperator<DiscreteCosine4Operator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) => GetDiscreteCosineCoefficient(Size, frequency, position); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the four-point inverse discrete sine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteSine4Operator : IHevcInverseTransformOperator<DiscreteSine4Operator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 4; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) => DiscreteSine4Coefficients[(frequency * Size) + position]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the eight-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine8Operator : IHevcInverseTransformOperator<DiscreteCosine8Operator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 8; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) => GetDiscreteCosineCoefficient(Size, frequency, position); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the sixteen-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine16Operator : IHevcInverseTransformOperator<DiscreteCosine16Operator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 16; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) => GetDiscreteCosineCoefficient(Size, frequency, position); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the thirty-two-point inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
private readonly struct DiscreteCosine32Operator : IHevcInverseTransformOperator<DiscreteCosine32Operator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static int Size => 32; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesButterfly => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static int GetCoefficient(int frequency, int position) => GetDiscreteCosineCoefficient(Size, frequency, position); |
|||
} |
|||
} |
|||
@ -0,0 +1,270 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Applies HEVC inverse transforms and reconstructs predicted samples.
|
|||
/// </summary>
|
|||
internal static partial class HevcInverseTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// The signed residual precision used after the second inverse-transform pass.
|
|||
/// </summary>
|
|||
private const int ResidualPrecision = 16; |
|||
|
|||
/// <summary>
|
|||
/// Defines one closed inverse-transform operation selected by the transform-unit syntax.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The implementing operator type.</typeparam>
|
|||
private interface IHevcInverseTransformOperator<TOperator> |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the transform side in samples.
|
|||
/// </summary>
|
|||
public static abstract int Size { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the transform uses the partial-butterfly factorization.
|
|||
/// </summary>
|
|||
public static abstract bool UsesButterfly { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets one inverse-transform matrix coefficient.
|
|||
/// </summary>
|
|||
/// <param name="frequency">The frequency-domain coordinate.</param>
|
|||
/// <param name="position">The spatial-domain coordinate.</param>
|
|||
/// <returns>The signed transform coefficient.</returns>
|
|||
public static abstract int GetCoefficient(int frequency, int position); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the scratch length required for the specified rectangular transform block.
|
|||
/// </summary>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <returns>The required number of signed thirty-two-bit elements.</returns>
|
|||
public static int GetScratchLength(int log2Width, int log2Height) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Width, 2, 5, nameof(log2Width)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Height, 2, 5, nameof(log2Height)); |
|||
return 2 << (log2Width + log2Height); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one transform block by adding its inverse-transformed residual to the predicted samples.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized transform coefficients in raster order.</param>
|
|||
/// <param name="destination">The predicted samples beginning at the transform-block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-by-four luma intra block uses the discrete sine transform.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int, int)"/>.</param>
|
|||
public static void TransformAdd( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int log2Width, |
|||
int log2Height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform, |
|||
Span<int> scratch) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Width, 2, 5, nameof(log2Width)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Height, 2, 5, nameof(log2Height)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(bitDepth, 8, 16, nameof(bitDepth)); |
|||
DebugGuard.IsTrue(!useDiscreteSineTransform || (log2Width == 2 && log2Height == 2), "The HEVC inverse DST is defined only for four-by-four blocks."); |
|||
|
|||
int width = 1 << log2Width; |
|||
int height = 1 << log2Height; |
|||
int sampleCount = width * height; |
|||
DebugGuard.IsTrue(coefficients.Length >= sampleCount, "The coefficient span is shorter than the transform block."); |
|||
DebugGuard.IsTrue(scratch.Length >= sampleCount * 2, "The scratch span is shorter than the inverse-transform requirement."); |
|||
|
|||
Span<int> first = scratch[..sampleCount]; |
|||
Span<int> second = scratch.Slice(sampleCount, sampleCount); |
|||
Span<int> residual = TransformCore( |
|||
coefficients[..sampleCount], |
|||
first, |
|||
second, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform); |
|||
|
|||
AddResidual(residual, destination, destinationStride, width, height, bitDepth); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies one two-dimensional inverse transform and writes signed residual samples in raster order.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized transform coefficients in raster order.</param>
|
|||
/// <param name="residual">The destination residual samples in raster order.</param>
|
|||
/// <param name="log2Width">The base-two logarithm of the transform-block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-by-four luma intra block uses the discrete sine transform.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int, int)"/>.</param>
|
|||
public static void Transform( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> residual, |
|||
int log2Width, |
|||
int log2Height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform, |
|||
Span<int> scratch) |
|||
{ |
|||
int width = 1 << log2Width; |
|||
int height = 1 << log2Height; |
|||
int sampleCount = width * height; |
|||
DebugGuard.IsTrue(residual.Length >= sampleCount, "The residual span is shorter than the transform block."); |
|||
DebugGuard.IsTrue(scratch.Length >= sampleCount * 2, "The scratch span is shorter than the inverse-transform requirement."); |
|||
|
|||
Span<int> transformed = TransformCore( |
|||
coefficients[..sampleCount], |
|||
scratch[..sampleCount], |
|||
scratch.Slice(sampleCount, sampleCount), |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform); |
|||
|
|||
transformed.CopyTo(residual); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dispatches both separable transform passes through closed operators selected from the block dimensions.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The complete dequantized coefficient block.</param>
|
|||
/// <param name="first">The first full-block scratch buffer.</param>
|
|||
/// <param name="second">The second full-block scratch buffer.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether both four-point passes use the discrete sine transform.</param>
|
|||
/// <returns>The scratch buffer containing the raster-ordered residual.</returns>
|
|||
private static Span<int> TransformCore( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> first, |
|||
Span<int> second, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform) |
|||
{ |
|||
int dynamicMinimum = -(1 << maxTransformDynamicRange); |
|||
int dynamicMaximum = (1 << maxTransformDynamicRange) - 1; |
|||
|
|||
// HEVC moves one normalization bit from the second pass to the first. The intermediate clip therefore
|
|||
// belongs after the vertical pass and must not be combined with the final residual clipping operation.
|
|||
Span<int> vertical = TransformDimension( |
|||
coefficients, |
|||
first, |
|||
second, |
|||
height, |
|||
width, |
|||
7, |
|||
dynamicMinimum, |
|||
dynamicMaximum, |
|||
useDiscreteSineTransform); |
|||
|
|||
Span<int> horizontalInput = vertical.Overlaps(first) ? second : first; |
|||
Transpose(vertical, horizontalInput, height, width); |
|||
|
|||
Span<int> horizontalWorkspace = horizontalInput.Overlaps(first) ? second : first; |
|||
int secondShift = maxTransformDynamicRange + 5 - bitDepth; |
|||
Span<int> horizontal = TransformDimension( |
|||
horizontalInput, |
|||
horizontalWorkspace, |
|||
horizontalInput, |
|||
width, |
|||
height, |
|||
secondShift, |
|||
-(1 << (ResidualPrecision - 1)), |
|||
(1 << (ResidualPrecision - 1)) - 1, |
|||
useDiscreteSineTransform); |
|||
|
|||
Span<int> residual = horizontal.Overlaps(first) ? second : first; |
|||
Transpose(horizontal, residual, width, height); |
|||
return residual; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the statically specialized operator for one transform dimension.
|
|||
/// </summary>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="initial">The initial operator output buffer.</param>
|
|||
/// <param name="alternate">The alternate combination buffer.</param>
|
|||
/// <param name="size">The transform dimension.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to the spatial results.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-point pass uses the discrete sine transform.</param>
|
|||
/// <returns>The buffer containing spatial rows followed by contiguous independent lines.</returns>
|
|||
private static Span<int> TransformDimension( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int size, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum, |
|||
bool useDiscreteSineTransform) |
|||
=> (size, useDiscreteSineTransform) switch |
|||
{ |
|||
(4, true) => TransformDimension<DiscreteSine4Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(4, false) => TransformDimension<DiscreteCosine4Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(8, _) => TransformDimension<DiscreteCosine8Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
(16, _) => TransformDimension<DiscreteCosine16Operator>(source, initial, alternate, lineCount, shift, minimum, maximum), |
|||
_ => TransformDimension<DiscreteCosine32Operator>(source, initial, alternate, lineCount, shift, minimum, maximum) |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Invokes one statically selected inverse-transform operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected inverse-transform operator.</typeparam>
|
|||
/// <param name="source">The frequency rows followed by contiguous independent lines.</param>
|
|||
/// <param name="initial">The initial operator output buffer.</param>
|
|||
/// <param name="alternate">The alternate combination buffer.</param>
|
|||
/// <param name="lineCount">The number of independent lines transformed together.</param>
|
|||
/// <param name="shift">The rounded right shift applied to the spatial results.</param>
|
|||
/// <param name="minimum">The inclusive output minimum.</param>
|
|||
/// <param name="maximum">The inclusive output maximum.</param>
|
|||
/// <returns>The buffer containing spatial rows followed by contiguous independent lines.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Span<int> TransformDimension<TOperator>( |
|||
ReadOnlySpan<int> source, |
|||
Span<int> initial, |
|||
Span<int> alternate, |
|||
int lineCount, |
|||
int shift, |
|||
int minimum, |
|||
int maximum) |
|||
where TOperator : struct, IHevcInverseTransformOperator<TOperator> |
|||
{ |
|||
if (!TOperator.UsesButterfly) |
|||
{ |
|||
TransformDense<TOperator>(source, initial, lineCount, shift, minimum, maximum); |
|||
return initial; |
|||
} |
|||
|
|||
PopulateButterflyGroups<TOperator>(source, initial, lineCount); |
|||
return CombineButterflyGroups<TOperator>(initial, alternate, lineCount, shift, minimum, maximum); |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
// 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-transform reconstruction.
|
|||
/// </summary>
|
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
public class HevcInverseTransformBenchmarks |
|||
{ |
|||
/// <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 dequantized coefficients reused by each benchmark block.
|
|||
/// </summary>
|
|||
private readonly int[] coefficients = new int[BlockSize * BlockSize]; |
|||
|
|||
/// <summary>
|
|||
/// The frame-wide predicted and reconstructed samples.
|
|||
/// </summary>
|
|||
private readonly ushort[] destination = new ushort[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// The maximum-block inverse-transform scratch reused throughout each coded frame.
|
|||
/// </summary>
|
|||
private readonly int[] scratch = new int[HevcInverseTransformer.GetScratchLength(BlockLog2, BlockLog2)]; |
|||
|
|||
/// <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.coefficients.Length; i++) |
|||
{ |
|||
this.coefficients[i] = (((i * 37) + 11) % 127) - 63; |
|||
} |
|||
|
|||
this.destination.AsSpan().Fill(2048); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures factorized inverse DCT, both transpositions, and saturated prediction addition for a coded frame.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark] |
|||
public ushort TransformFrame() |
|||
{ |
|||
for (int y = 0; y < Height; y += BlockSize) |
|||
{ |
|||
for (int x = 0; x < Width; x += BlockSize) |
|||
{ |
|||
HevcInverseTransformer.TransformAdd( |
|||
this.coefficients, |
|||
this.destination.AsSpan((y * Width) + x), |
|||
Width, |
|||
BlockLog2, |
|||
BlockLog2, |
|||
12, |
|||
18, |
|||
false, |
|||
this.scratch); |
|||
} |
|||
} |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
} |
|||
@ -0,0 +1,248 @@ |
|||
// 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-DCT, inverse-DST, rectangular-block, dynamic-range, and reconstruction behavior.
|
|||
/// </summary>
|
|||
[Trait("Format", "Heic")] |
|||
public class HevcInverseTransformerTests |
|||
{ |
|||
/// <summary>
|
|||
/// Verifies the two normative normalization stages with a pure DC coefficient and a strided prediction block.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DctDcCoefficientProducesUniformReconstruction() |
|||
{ |
|||
const int size = 4; |
|||
const int stride = 7; |
|||
int[] coefficients = new int[size * size]; |
|||
coefficients[0] = 1024; |
|||
ushort[] destination = new ushort[stride * size]; |
|||
destination.AsSpan().Fill(100); |
|||
int[] scratch = new int[HevcInverseTransformer.GetScratchLength(2, 2)]; |
|||
|
|||
HevcInverseTransformer.TransformAdd(coefficients, destination, stride, 2, 2, 8, 15, false, scratch); |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
Assert.Equal((ushort)108, destination[(y * stride) + x]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies a hand-derived four-by-four inverse-DST result from a pure lowest-frequency coefficient.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void DstLowestFrequencyMatchesFixedResult() |
|||
{ |
|||
int[] coefficients = new int[16]; |
|||
coefficients[0] = 1024; |
|||
int[] actual = new int[16]; |
|||
int[] scratch = new int[HevcInverseTransformer.GetScratchLength(2, 2)]; |
|||
int[] expected = |
|||
[ |
|||
2, 3, 4, 5, |
|||
3, 6, 8, 9, |
|||
4, 8, 11, 12, |
|||
5, 9, 12, 14 |
|||
]; |
|||
|
|||
HevcInverseTransformer.Transform(coefficients, actual, 2, 2, 8, 15, true, scratch); |
|||
|
|||
Assert.True(expected.AsSpan().SequenceEqual(actual)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares factorized SIMD transforms with a dense specification-shaped oracle across supported dimensions and precisions.
|
|||
/// </summary>
|
|||
/// <param name="log2Width">The base-two logarithm of the tested block width.</param>
|
|||
/// <param name="log2Height">The base-two logarithm of the tested block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the tested four-by-four block uses the inverse DST.</param>
|
|||
[Theory] |
|||
[InlineData(2, 2, 8, 15, false)] |
|||
[InlineData(2, 2, 8, 15, true)] |
|||
[InlineData(2, 3, 10, 15, false)] |
|||
[InlineData(3, 2, 10, 15, false)] |
|||
[InlineData(3, 3, 10, 15, false)] |
|||
[InlineData(4, 3, 12, 18, false)] |
|||
[InlineData(3, 4, 12, 18, false)] |
|||
[InlineData(5, 4, 12, 18, false)] |
|||
[InlineData(4, 5, 12, 18, false)] |
|||
[InlineData(5, 5, 12, 15, false)] |
|||
public void TransformMatchesDenseOracle( |
|||
int log2Width, |
|||
int log2Height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform) |
|||
{ |
|||
int width = 1 << log2Width; |
|||
int height = 1 << log2Height; |
|||
int sampleCount = width * height; |
|||
int[] coefficients = new int[sampleCount]; |
|||
int coefficientMaximum = (1 << maxTransformDynamicRange) - 1; |
|||
for (int i = 0; i < coefficients.Length; i++) |
|||
{ |
|||
// Alternating values across the complete dequantized range exercise both intermediate clipping bounds
|
|||
// as well as every frequency group used by the factorized transform.
|
|||
int unit = (((i * 37) + (width * 11) + height) % 127) - 63; |
|||
coefficients[i] = (unit * coefficientMaximum) / 63; |
|||
} |
|||
|
|||
int[] expected = new int[sampleCount]; |
|||
int[] actual = new int[sampleCount]; |
|||
int[] oracleScratch = new int[sampleCount]; |
|||
int[] transformScratch = new int[HevcInverseTransformer.GetScratchLength(log2Width, log2Height)]; |
|||
TransformDenseOracle( |
|||
coefficients, |
|||
expected, |
|||
oracleScratch, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform); |
|||
|
|||
HevcInverseTransformer.Transform( |
|||
coefficients, |
|||
actual, |
|||
log2Width, |
|||
log2Height, |
|||
bitDepth, |
|||
maxTransformDynamicRange, |
|||
useDiscreteSineTransform, |
|||
transformScratch); |
|||
|
|||
Assert.True(expected.AsSpan().SequenceEqual(actual), $"The {width}x{height} transform at {bitDepth} bits did not match the dense HEVC oracle."); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that residual addition clips both negative and positive reconstruction overflow at the component range.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void TransformAddClipsToComponentRange() |
|||
{ |
|||
const int size = 4; |
|||
int[] positiveCoefficients = new int[size * size]; |
|||
int[] negativeCoefficients = new int[size * size]; |
|||
positiveCoefficients[0] = 32767; |
|||
negativeCoefficients[0] = -32768; |
|||
ushort[] positive = new ushort[size * size]; |
|||
ushort[] negative = new ushort[size * size]; |
|||
positive.AsSpan().Fill(4090); |
|||
negative.AsSpan().Fill(5); |
|||
int[] scratch = new int[HevcInverseTransformer.GetScratchLength(2, 2)]; |
|||
|
|||
HevcInverseTransformer.TransformAdd(positiveCoefficients, positive, size, 2, 2, 12, 18, false, scratch); |
|||
HevcInverseTransformer.TransformAdd(negativeCoefficients, negative, size, 2, 2, 12, 18, false, scratch); |
|||
|
|||
Assert.All(positive, value => Assert.Equal((ushort)4095, value)); |
|||
Assert.All(negative, value => Assert.Equal((ushort)0, value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies both inverse-transform dimensions using direct matrix products and normative rounding points.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The dequantized coefficient block.</param>
|
|||
/// <param name="residual">The destination signed residual block.</param>
|
|||
/// <param name="intermediate">The full-block intermediate buffer.</param>
|
|||
/// <param name="width">The transform-block width.</param>
|
|||
/// <param name="height">The transform-block height.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="maxTransformDynamicRange">The transform dynamic range excluding its sign bit.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether both dimensions use the four-point inverse DST.</param>
|
|||
private static void TransformDenseOracle( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> residual, |
|||
Span<int> intermediate, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
int maxTransformDynamicRange, |
|||
bool useDiscreteSineTransform) |
|||
{ |
|||
int dynamicMinimum = -(1 << maxTransformDynamicRange); |
|||
int dynamicMaximum = (1 << maxTransformDynamicRange) - 1; |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int xFrequency = 0; xFrequency < width; xFrequency++) |
|||
{ |
|||
int sum = 0; |
|||
for (int yFrequency = 0; yFrequency < height; yFrequency++) |
|||
{ |
|||
sum += coefficients[(yFrequency * width) + xFrequency] * GetInverseCoefficient(height, yFrequency, y, useDiscreteSineTransform); |
|||
} |
|||
|
|||
intermediate[(y * width) + xFrequency] = Math.Clamp((sum + 64) >> 7, dynamicMinimum, dynamicMaximum); |
|||
} |
|||
} |
|||
|
|||
int secondShift = maxTransformDynamicRange + 5 - bitDepth; |
|||
int secondRounding = 1 << (secondShift - 1); |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int sum = 0; |
|||
for (int xFrequency = 0; xFrequency < width; xFrequency++) |
|||
{ |
|||
sum += intermediate[(y * width) + xFrequency] * GetInverseCoefficient(width, xFrequency, x, useDiscreteSineTransform); |
|||
} |
|||
|
|||
residual[(y * width) + x] = Math.Clamp((sum + secondRounding) >> secondShift, short.MinValue, short.MaxValue); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets one coefficient from the normative HEVC inverse-DCT or four-point inverse-DST matrix.
|
|||
/// </summary>
|
|||
/// <param name="size">The transform dimension.</param>
|
|||
/// <param name="frequency">The frequency-domain coordinate.</param>
|
|||
/// <param name="position">The spatial-domain coordinate.</param>
|
|||
/// <param name="useDiscreteSineTransform">Whether the four-point inverse DST is selected.</param>
|
|||
/// <returns>The signed matrix coefficient.</returns>
|
|||
private static int GetInverseCoefficient(int size, int frequency, int position, bool useDiscreteSineTransform) |
|||
{ |
|||
if (useDiscreteSineTransform) |
|||
{ |
|||
ReadOnlySpan<sbyte> sine = |
|||
[ |
|||
29, 55, 74, 84, |
|||
74, 74, 0, -74, |
|||
84, -29, -74, 55, |
|||
55, -84, 74, -29 |
|||
]; |
|||
|
|||
return sine[(frequency * 4) + position]; |
|||
} |
|||
|
|||
if (frequency == 0) |
|||
{ |
|||
return 64; |
|||
} |
|||
|
|||
ReadOnlySpan<sbyte> magnitudes = |
|||
[ |
|||
90, 90, 90, 90, 89, 88, 87, 85, 83, 82, 80, 78, 75, 73, 70, 67, 64, |
|||
61, 57, 54, 50, 46, 43, 38, 36, 31, 25, 22, 18, 13, 9, 4, 0 |
|||
]; |
|||
|
|||
int angle = (((2 * position) + 1) * frequency * (32 / size)) & 127; |
|||
if (angle > 64) |
|||
{ |
|||
angle = 128 - angle; |
|||
} |
|||
|
|||
return angle > 32 ? -magnitudes[64 - angle] : magnitudes[angle]; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue