mirror of https://github.com/SixLabors/ImageSharp
6 changed files with 1634 additions and 1 deletions
@ -0,0 +1,423 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Provides shared SIMD operations used by the closed prediction operators.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Calculates one 512-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<uint> CalculatePlanarVector( |
|||
Vector512<uint> top, |
|||
Vector512<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector512<uint> horizontal = ((Vector512.Create(size - 1) - indices) * left) + ((indices + Vector512<uint>.One) * topRight); |
|||
Vector512<uint> vertical = (top * topWeight) + Vector512.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector512.Create(rounding)) >> shift; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one 256-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<uint> CalculatePlanarVector( |
|||
Vector256<uint> top, |
|||
Vector256<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector256<uint> horizontal = ((Vector256.Create(size - 1) - indices) * left) + ((indices + Vector256<uint>.One) * topRight); |
|||
Vector256<uint> vertical = (top * topWeight) + Vector256.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector256.Create(rounding)) >> shift; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Calculates one 128-bit half of a planar prediction row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="indices">The zero-based X coordinates.</param>
|
|||
/// <param name="left">The left reference sample for the row.</param>
|
|||
/// <param name="topRight">The top-right reference sample.</param>
|
|||
/// <param name="bottomLeft">The bottom-left reference sample.</param>
|
|||
/// <param name="topWeight">The top-reference weight.</param>
|
|||
/// <param name="bottomWeight">The bottom-left-reference weight.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="rounding">The division rounding constant.</param>
|
|||
/// <param name="shift">The division shift.</param>
|
|||
/// <returns>The predicted samples as widened lanes.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<uint> CalculatePlanarVector( |
|||
Vector128<uint> top, |
|||
Vector128<uint> indices, |
|||
uint left, |
|||
uint topRight, |
|||
uint bottomLeft, |
|||
uint topWeight, |
|||
uint bottomWeight, |
|||
uint size, |
|||
uint rounding, |
|||
int shift) |
|||
{ |
|||
Vector128<uint> horizontal = ((Vector128.Create(size - 1) - indices) * left) + ((indices + Vector128<uint>.One) * topRight); |
|||
Vector128<uint> vertical = (top * topWeight) + Vector128.Create(bottomLeft * bottomWeight); |
|||
return (horizontal + vertical + Vector128.Create(rounding)) >> shift; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sums reconstructed reference samples without overflowing their 16-bit storage.
|
|||
/// </summary>
|
|||
/// <param name="samples">The samples to sum.</param>
|
|||
/// <returns>The exact unsigned sum.</returns>
|
|||
private static uint SumSamples(ReadOnlySpan<ushort> samples) |
|||
{ |
|||
ref ushort samplesBase = ref MemoryMarshal.GetReference(samples); |
|||
uint sum = 0; |
|||
int i = 0; |
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
(Vector512<uint> low, Vector512<uint> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector512.Sum(low) + Vector512.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
(Vector256<uint> low, Vector256<uint> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector256.Sum(low) + Vector256.Sum(high); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = samples.Length - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
(Vector128<uint> low, Vector128<uint> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)i)); |
|||
sum += Vector128.Sum(low) + Vector128.Sum(high); |
|||
} |
|||
} |
|||
|
|||
for (; i < samples.Length; i++) |
|||
{ |
|||
sum += Unsafe.Add(ref samplesBase, i); |
|||
} |
|||
|
|||
return sum; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies the top reference into every row and optionally filters the first column.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the vertical luma edge filter applies.</param>
|
|||
private static void PredictVertical( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
ReadOnlySpan<ushort> row = top.Slice(1, size); |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
row.CopyTo(destination.Slice(y * destinationStride, size)); |
|||
if (filterPredictionEdges) |
|||
{ |
|||
int sample = destination[y * destinationStride] + ((left[y + 1] - left[0]) >> 1); |
|||
destination[y * destinationStride] = (ushort)Math.Clamp(sample, 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills each row from its left reference and optionally filters the first row.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the horizontal luma edge filter applies.</param>
|
|||
private static void PredictHorizontal( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(left[y + 1]); |
|||
} |
|||
|
|||
if (!filterPredictionEdges) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
int sample = destination[x] + ((top[x + 1] - top[0]) >> 1); |
|||
destination[x] = (ushort)Math.Clamp(sample, 0, maximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Generates a vertical-oriented angular block using contiguous SIMD interpolation within each row.
|
|||
/// </summary>
|
|||
/// <param name="main">The main reference beginning at logical index zero.</param>
|
|||
/// <param name="mainOrigin">The span index corresponding to logical reference index zero.</param>
|
|||
/// <param name="destination">The contiguous destination or transposition scratch block.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="angle">The signed prediction displacement in thirty-second-sample units.</param>
|
|||
private static void PredictAngularRows( |
|||
ReadOnlySpan<ushort> main, |
|||
int mainOrigin, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int angle) |
|||
{ |
|||
for (int y = 0, deltaPosition = angle; y < size; y++, deltaPosition += angle) |
|||
{ |
|||
int deltaInteger = deltaPosition >> 5; |
|||
int deltaFraction = deltaPosition & 31; |
|||
int sourceOffset = mainOrigin + deltaInteger + 1; |
|||
Span<ushort> row = destination.Slice(y * destinationStride, size); |
|||
if (deltaFraction == 0) |
|||
{ |
|||
main.Slice(sourceOffset, size).CopyTo(row); |
|||
} |
|||
else |
|||
{ |
|||
InterpolateAngularRow(main[sourceOffset..], row, deltaFraction); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interpolates one angular prediction row between consecutive main-reference samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The first main-reference sample for the row.</param>
|
|||
/// <param name="destination">The destination prediction row.</param>
|
|||
/// <param name="fraction">The right-hand weight with a denominator of thirty-two.</param>
|
|||
private static void InterpolateAngularRow(ReadOnlySpan<ushort> source, Span<ushort> destination, int fraction) |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
uint leftWeight = (uint)(32 - fraction); |
|||
uint rightWeight = (uint)fraction; |
|||
int i = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> left = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector512<ushort> right = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector512<uint> leftLow, Vector512<uint> leftHigh) = Vector512.Widen(left); |
|||
(Vector512<uint> rightLow, Vector512<uint> rightHigh) = Vector512.Widen(right); |
|||
Vector512<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector512.Create(16U)) >> 5; |
|||
Vector512<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector512.Create(16U)) >> 5; |
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> left = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector256<ushort> right = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector256<uint> leftLow, Vector256<uint> leftHigh) = Vector256.Widen(left); |
|||
(Vector256<uint> rightLow, Vector256<uint> rightHigh) = Vector256.Widen(right); |
|||
Vector256<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector256.Create(16U)) >> 5; |
|||
Vector256<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector256.Create(16U)) >> 5; |
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = destination.Length - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> left = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector128<ushort> right = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector128<uint> leftLow, Vector128<uint> leftHigh) = Vector128.Widen(left); |
|||
(Vector128<uint> rightLow, Vector128<uint> rightHigh) = Vector128.Widen(right); |
|||
Vector128<uint> low = ((leftLow * leftWeight) + (rightLow * rightWeight) + Vector128.Create(16U)) >> 5; |
|||
Vector128<uint> high = ((leftHigh * leftWeight) + (rightHigh * rightWeight) + Vector128.Create(16U)) >> 5; |
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < destination.Length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)(((source[i] * leftWeight) + (source[i + 1] * rightWeight) + 16) >> 5); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes a square horizontal prediction block into the reconstructed destination.
|
|||
/// </summary>
|
|||
/// <param name="source">The contiguous transposed prediction block.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
private static void TransposeBlock(ReadOnlySpan<ushort> source, Span<ushort> destination, int destinationStride, int size) |
|||
{ |
|||
if (Vector128.IsHardwareAccelerated && size >= Vector128<ushort>.Count) |
|||
{ |
|||
for (int y = 0; y < size; y += Vector128<ushort>.Count) |
|||
{ |
|||
for (int x = 0; x < size; x += Vector128<ushort>.Count) |
|||
{ |
|||
Transpose8x8(source, destination, destinationStride, size, x, y); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
destination[(x * destinationStride) + y] = source[(y * size) + x]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transposes one eight-by-eight tile of 16-bit prediction samples.
|
|||
/// </summary>
|
|||
/// <param name="source">The contiguous source block.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="sourceStride">The contiguous source row stride.</param>
|
|||
/// <param name="x">The tile X coordinate in the source block.</param>
|
|||
/// <param name="y">The tile Y coordinate in the source block.</param>
|
|||
private static void Transpose8x8( |
|||
ReadOnlySpan<ushort> source, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int sourceStride, |
|||
int x, |
|||
int y) |
|||
{ |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
Vector128<short> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x)).AsInt16(); |
|||
Vector128<short> row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x)).AsInt16(); |
|||
|
|||
// Three zip stages exchange one, two, then four 16-bit coordinates. The resulting vectors are the eight
|
|||
// source columns in row order, so each can be stored contiguously into one destination row.
|
|||
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1); |
|||
Vector128<short> pair1 = Vector128_.UnpackHigh(row0, row1); |
|||
Vector128<short> pair2 = Vector128_.UnpackLow(row2, row3); |
|||
Vector128<short> pair3 = Vector128_.UnpackHigh(row2, row3); |
|||
Vector128<short> pair4 = Vector128_.UnpackLow(row4, row5); |
|||
Vector128<short> pair5 = Vector128_.UnpackHigh(row4, row5); |
|||
Vector128<short> pair6 = Vector128_.UnpackLow(row6, row7); |
|||
Vector128<short> pair7 = Vector128_.UnpackHigh(row6, row7); |
|||
Vector128<int> quad0 = Vector128_.UnpackLow(pair0.AsInt32(), pair2.AsInt32()); |
|||
Vector128<int> quad1 = Vector128_.UnpackHigh(pair0.AsInt32(), pair2.AsInt32()); |
|||
Vector128<int> quad2 = Vector128_.UnpackLow(pair1.AsInt32(), pair3.AsInt32()); |
|||
Vector128<int> quad3 = Vector128_.UnpackHigh(pair1.AsInt32(), pair3.AsInt32()); |
|||
Vector128<int> quad4 = Vector128_.UnpackLow(pair4.AsInt32(), pair6.AsInt32()); |
|||
Vector128<int> quad5 = Vector128_.UnpackHigh(pair4.AsInt32(), pair6.AsInt32()); |
|||
Vector128<int> quad6 = Vector128_.UnpackLow(pair5.AsInt32(), pair7.AsInt32()); |
|||
Vector128<int> quad7 = Vector128_.UnpackHigh(pair5.AsInt32(), pair7.AsInt32()); |
|||
Vector128<ushort> column0 = Vector128_.UnpackLow(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column1 = Vector128_.UnpackHigh(quad0.AsInt64(), quad4.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column2 = Vector128_.UnpackLow(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column3 = Vector128_.UnpackHigh(quad1.AsInt64(), quad5.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column4 = Vector128_.UnpackLow(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column5 = Vector128_.UnpackHigh(quad2.AsInt64(), quad6.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column6 = Vector128_.UnpackLow(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
|||
Vector128<ushort> column7 = Vector128_.UnpackHigh(quad3.AsInt64(), quad7.AsInt64()).AsUInt16(); |
|||
column0.StoreUnsafe(ref destinationBase, (nuint)(((x + 0) * destinationStride) + y)); |
|||
column1.StoreUnsafe(ref destinationBase, (nuint)(((x + 1) * destinationStride) + y)); |
|||
column2.StoreUnsafe(ref destinationBase, (nuint)(((x + 2) * destinationStride) + y)); |
|||
column3.StoreUnsafe(ref destinationBase, (nuint)(((x + 3) * destinationStride) + y)); |
|||
column4.StoreUnsafe(ref destinationBase, (nuint)(((x + 4) * destinationStride) + y)); |
|||
column5.StoreUnsafe(ref destinationBase, (nuint)(((x + 5) * destinationStride) + y)); |
|||
column6.StoreUnsafe(ref destinationBase, (nuint)(((x + 6) * destinationStride) + y)); |
|||
column7.StoreUnsafe(ref destinationBase, (nuint)(((x + 7) * destinationStride) + y)); |
|||
} |
|||
} |
|||
@ -0,0 +1,280 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
/// <content>
|
|||
/// Provides the closed planar, DC, and angular prediction operators.
|
|||
/// </content>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Implements planar interpolation between the top, left, bottom-left, and top-right references.
|
|||
/// </summary>
|
|||
private readonly struct PlanarPredictionOperator : IHevcIntraPredictionOperator<PlanarPredictionOperator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
ref ushort topBase = ref MemoryMarshal.GetReference(top); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
uint bottomLeft = left[size]; |
|||
uint topRight = top[size]; |
|||
int shift = BitOperations.Log2((uint)size) + 1; |
|||
uint rounding = (uint)size; |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
uint leftSample = left[y + 1]; |
|||
uint topWeight = (uint)(size - y - 1); |
|||
uint bottomWeight = (uint)(y + 1); |
|||
ref ushort rowBase = ref Unsafe.Add(ref destinationBase, y * destinationStride); |
|||
int x = 0; |
|||
|
|||
// The two widened halves carry consecutive X coordinates. Each lane evaluates the normative
|
|||
// horizontal and vertical ramps, then narrows after the common rounded power-of-two division.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<uint> indices = CreateIndicesVector512(); |
|||
int oneVectorFromEnd = size - Vector512<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> topSamples = Vector512.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector512<uint> topLow, Vector512<uint> topHigh) = Vector512.Widen(topSamples); |
|||
Vector512<uint> lowIndices = indices + Vector512.Create((uint)x); |
|||
Vector512<uint> highIndices = lowIndices + Vector512.Create((uint)Vector512<uint>.Count); |
|||
Vector512<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector512<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<uint> indices = CreateIndicesVector256(); |
|||
int oneVectorFromEnd = size - Vector256<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> topSamples = Vector256.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector256<uint> topLow, Vector256<uint> topHigh) = Vector256.Widen(topSamples); |
|||
Vector256<uint> lowIndices = indices + Vector256.Create((uint)x); |
|||
Vector256<uint> highIndices = lowIndices + Vector256.Create((uint)Vector256<uint>.Count); |
|||
Vector256<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector256<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<uint> indices = CreateIndicesVector128(); |
|||
int oneVectorFromEnd = size - Vector128<ushort>.Count; |
|||
for (; x <= oneVectorFromEnd; x += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> topSamples = Vector128.LoadUnsafe(ref topBase, (nuint)(x + 1)); |
|||
(Vector128<uint> topLow, Vector128<uint> topHigh) = Vector128.Widen(topSamples); |
|||
Vector128<uint> lowIndices = indices + Vector128.Create((uint)x); |
|||
Vector128<uint> highIndices = lowIndices + Vector128.Create((uint)Vector128<uint>.Count); |
|||
Vector128<uint> low = CalculatePlanarVector( |
|||
topLow, |
|||
lowIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector128<uint> high = CalculatePlanarVector( |
|||
topHigh, |
|||
highIndices, |
|||
leftSample, |
|||
topRight, |
|||
bottomLeft, |
|||
topWeight, |
|||
bottomWeight, |
|||
(uint)size, |
|||
rounding, |
|||
shift); |
|||
|
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref rowBase, x)); |
|||
} |
|||
} |
|||
|
|||
for (; x < size; x++) |
|||
{ |
|||
uint horizontal = ((uint)(size - x - 1) * leftSample) + ((uint)(x + 1) * topRight); |
|||
uint vertical = ((uint)(size - y - 1) * top[x + 1]) + ((uint)(y + 1) * bottomLeft); |
|||
Unsafe.Add(ref rowBase, x) = (ushort)((horizontal + vertical + (uint)size) >> shift); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements DC prediction and its optional luma boundary filter.
|
|||
/// </summary>
|
|||
private readonly struct DcPredictionOperator : IHevcIntraPredictionOperator<DcPredictionOperator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
uint sum = SumSamples(top.Slice(1, size)) + SumSamples(left.Slice(1, size)); |
|||
ushort dc = (ushort)((sum + (uint)size) >> (BitOperations.Log2((uint)size) + 1)); |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(dc); |
|||
} |
|||
|
|||
if (!filterPredictionEdges) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
destination[0] = (ushort)((top[1] + left[1] + (2 * dc) + 2) >> 2); |
|||
for (int x = 1; x < size; x++) |
|||
{ |
|||
destination[x] = (ushort)((top[x + 1] + (3 * dc) + 2) >> 2); |
|||
} |
|||
|
|||
for (int y = 1; y < size; y++) |
|||
{ |
|||
destination[y * destinationStride] = (ushort)((left[y + 1] + (3 * dc) + 2) >> 2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements the thirty-three directional intra-prediction modes.
|
|||
/// </summary>
|
|||
private readonly struct AngularPredictionOperator : IHevcIntraPredictionOperator<AngularPredictionOperator> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
if (mode == VerticalMode) |
|||
{ |
|||
PredictVertical(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
if (mode == HorizontalMode) |
|||
{ |
|||
PredictHorizontal(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
bool vertical = mode >= FirstVerticalMode; |
|||
int angleMode = vertical ? mode - VerticalMode : HorizontalMode - mode; |
|||
int absoluteAngleMode = Math.Abs(angleMode); |
|||
int angle = PredictionAngles[absoluteAngleMode] * Math.Sign(angleMode); |
|||
ReadOnlySpan<ushort> main = vertical ? top : left; |
|||
ReadOnlySpan<ushort> side = vertical ? left : top; |
|||
Span<ushort> temporaryBlock = scratch[..(size * size)]; |
|||
Span<ushort> extendedReference = scratch.Slice(size * size, (4 * size) + 1); |
|||
int mainOrigin = 0; |
|||
|
|||
if (angle < 0) |
|||
{ |
|||
mainOrigin = size * 2; |
|||
main[..(size + 1)].CopyTo(extendedReference[mainOrigin..]); |
|||
int inverseAngle = InversePredictionAngles[absoluteAngleMode]; |
|||
int inverseAngleSum = 128; |
|||
int minimumIndex = (size * angle) >> 5; |
|||
for (int index = -1; index > minimumIndex; index--) |
|||
{ |
|||
inverseAngleSum += inverseAngle; |
|||
extendedReference[mainOrigin + index] = side[inverseAngleSum >> 8]; |
|||
} |
|||
|
|||
main = extendedReference; |
|||
} |
|||
|
|||
Span<ushort> prediction = vertical ? destination : temporaryBlock; |
|||
int predictionStride = vertical ? destinationStride : size; |
|||
PredictAngularRows(main, mainOrigin, prediction, predictionStride, size, angle); |
|||
if (!vertical) |
|||
{ |
|||
TransposeBlock(temporaryBlock, destination, destinationStride, size); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,412 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
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 HEVC intra-prediction blocks from prepared neighboring samples.
|
|||
/// </summary>
|
|||
internal static partial class HevcIntraPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// The HEVC planar prediction mode.
|
|||
/// </summary>
|
|||
private const int PlanarMode = 0; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC DC prediction mode.
|
|||
/// </summary>
|
|||
private const int DcMode = 1; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC horizontal prediction mode.
|
|||
/// </summary>
|
|||
private const int HorizontalMode = 10; |
|||
|
|||
/// <summary>
|
|||
/// The first prediction mode whose main reference is the top row.
|
|||
/// </summary>
|
|||
private const int FirstVerticalMode = 18; |
|||
|
|||
/// <summary>
|
|||
/// The HEVC vertical prediction mode.
|
|||
/// </summary>
|
|||
private const int VerticalMode = 26; |
|||
|
|||
/// <summary>
|
|||
/// The largest transform-block side supported by HEVC intra prediction.
|
|||
/// </summary>
|
|||
private const int MaximumBlockSize = 32; |
|||
|
|||
/// <summary>
|
|||
/// Defines one closed intra-prediction operation selected by the decoded mode.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The implementing operator type.</typeparam>
|
|||
private interface IHevcIntraPredictionOperator<TOperator> |
|||
where TOperator : struct, IHevcIntraPredictionOperator<TOperator> |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs one square prediction block.
|
|||
/// </summary>
|
|||
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
|||
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
|||
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="size">The square block side in samples.</param>
|
|||
/// <param name="mode">The decoded prediction mode.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
|||
/// <param name="scratch">The caller-owned block and extended-reference scratch space.</param>
|
|||
public static abstract void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the angle selected by each absolute angular-mode displacement.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> PredictionAngles => [0, 2, 5, 9, 13, 17, 21, 26, 32]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the reciprocal angle used to extend the main reference for negative directions.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> InversePredictionAngles => [0, 4096, 1638, 910, 630, 482, 390, 315, 256]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the scratch length required to predict a block of the specified size.
|
|||
/// </summary>
|
|||
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
|||
/// <returns>The required number of <see cref="ushort"/> elements.</returns>
|
|||
public static int GetScratchLength(int log2Size) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
int size = 1 << log2Size; |
|||
return (size * size) + (4 * size) + 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one square intra-prediction block using a closed operator selected by the decoded mode.
|
|||
/// </summary>
|
|||
/// <param name="top">The top-left, top, and top-right reference samples.</param>
|
|||
/// <param name="left">The top-left, left, and below-left reference samples.</param>
|
|||
/// <param name="destination">The destination buffer beginning at the block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square block side.</param>
|
|||
/// <param name="mode">The decoded prediction mode in the inclusive range zero through thirty-four.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies to the selected block.</param>
|
|||
/// <param name="scratch">The caller-owned scratch returned by <see cref="GetScratchLength(int)"/>.</param>
|
|||
public static void Predict( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int log2Size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
DebugGuard.MustBeBetweenOrEqualTo(mode, PlanarMode, 34, nameof(mode)); |
|||
int size = 1 << log2Size; |
|||
|
|||
switch (mode) |
|||
{ |
|||
case PlanarMode: |
|||
Predict<PlanarPredictionOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
case DcMode: |
|||
Predict<DcPredictionOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
default: |
|||
Predict<AngularPredictionOperator>( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Filters prepared reference samples using the normative three-tap or strong bilinear filter.
|
|||
/// </summary>
|
|||
/// <param name="top">The unfiltered top-left, top, and top-right samples.</param>
|
|||
/// <param name="left">The unfiltered top-left, left, and below-left samples.</param>
|
|||
/// <param name="filteredTop">The destination top reference.</param>
|
|||
/// <param name="filteredLeft">The destination left reference.</param>
|
|||
/// <param name="log2Size">The base-two logarithm of the square prediction-block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed luma precision.</param>
|
|||
/// <param name="strongIntraSmoothingEnabled">Whether the sequence permits strong intra smoothing.</param>
|
|||
public static void FilterReferenceSamples( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> filteredTop, |
|||
Span<ushort> filteredLeft, |
|||
int log2Size, |
|||
int bitDepth, |
|||
bool strongIntraSmoothingEnabled) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); |
|||
int size = 1 << log2Size; |
|||
int referenceLength = (size * 2) + 1; |
|||
bool useStrongSmoothing = strongIntraSmoothingEnabled && size == MaximumBlockSize; |
|||
if (useStrongSmoothing) |
|||
{ |
|||
int threshold = 1 << (bitDepth - 5); |
|||
int last = referenceLength - 1; |
|||
bool leftIsBilinear = Math.Abs((left[last] + left[0]) - (2 * left[size])) < threshold; |
|||
bool topIsBilinear = Math.Abs((top[0] + top[last]) - (2 * top[size])) < threshold; |
|||
useStrongSmoothing = leftIsBilinear && topIsBilinear; |
|||
} |
|||
|
|||
if (useStrongSmoothing) |
|||
{ |
|||
FilterReferenceBilinear(top[..referenceLength], filteredTop, size); |
|||
FilterReferenceBilinear(left[..referenceLength], filteredLeft, size); |
|||
return; |
|||
} |
|||
|
|||
// The corner belongs to both references. Filtering it once from the first samples on both sides keeps the
|
|||
// two logical arrays identical at index zero before their independent one-dimensional filters continue.
|
|||
ushort filteredCorner = (ushort)((left[1] + (2 * top[0]) + top[1] + 2) >> 2); |
|||
filteredTop[0] = filteredCorner; |
|||
filteredLeft[0] = filteredCorner; |
|||
FilterReferenceThreeTap(top[..referenceLength], filteredTop); |
|||
FilterReferenceThreeTap(left[..referenceLength], filteredLeft); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Invokes one statically selected prediction operator without interface dispatch in the block loop.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The selected prediction operator.</typeparam>
|
|||
/// <param name="top">The prepared top reference.</param>
|
|||
/// <param name="left">The prepared left reference.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride in samples.</param>
|
|||
/// <param name="size">The square block side in samples.</param>
|
|||
/// <param name="mode">The decoded prediction mode.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
/// <param name="scratch">The caller-owned prediction scratch.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void Predict<TOperator>( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges, |
|||
Span<ushort> scratch) |
|||
where TOperator : struct, IHevcIntraPredictionOperator<TOperator> |
|||
=> TOperator.Predict( |
|||
top, |
|||
left, |
|||
destination, |
|||
destinationStride, |
|||
size, |
|||
mode, |
|||
bitDepth, |
|||
filterPredictionEdges, |
|||
scratch); |
|||
|
|||
/// <summary>
|
|||
/// Applies the strong bilinear filter between the reference endpoints.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete unfiltered reference.</param>
|
|||
/// <param name="destination">The complete filtered reference.</param>
|
|||
/// <param name="size">The prediction-block side in samples.</param>
|
|||
private static void FilterReferenceBilinear(ReadOnlySpan<ushort> source, Span<ushort> destination, int size) |
|||
{ |
|||
int last = source.Length - 1; |
|||
destination[0] = source[0]; |
|||
destination[last] = source[last]; |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
uint first = source[0]; |
|||
uint final = source[last]; |
|||
int shift = BitOperations.Log2((uint)(size * 2)); |
|||
uint rounding = (uint)size; |
|||
int i = 1; |
|||
|
|||
// Each widened lane represents one reference coordinate. The weights sum to 2N, so narrowing is exact
|
|||
// after the rounded shift for every supported 8, 10, and 12-bit sample.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<uint> indices = CreateIndicesVector512(); |
|||
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<uint> lowerIndices = indices + Vector512.Create((uint)i); |
|||
Vector512<uint> upperIndices = lowerIndices + Vector512.Create((uint)Vector512<uint>.Count); |
|||
Vector512<uint> lower = (((Vector512.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector512.Create(rounding)) >> shift; |
|||
Vector512<uint> upper = (((Vector512.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector512.Create(rounding)) >> shift; |
|||
Vector512.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<uint> indices = CreateIndicesVector256(); |
|||
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<uint> lowerIndices = indices + Vector256.Create((uint)i); |
|||
Vector256<uint> upperIndices = lowerIndices + Vector256.Create((uint)Vector256<uint>.Count); |
|||
Vector256<uint> lower = (((Vector256.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector256.Create(rounding)) >> shift; |
|||
Vector256<uint> upper = (((Vector256.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector256.Create(rounding)) >> shift; |
|||
Vector256.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<uint> indices = CreateIndicesVector128(); |
|||
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<uint> lowerIndices = indices + Vector128.Create((uint)i); |
|||
Vector128<uint> upperIndices = lowerIndices + Vector128.Create((uint)Vector128<uint>.Count); |
|||
Vector128<uint> lower = (((Vector128.Create((uint)last) - lowerIndices) * first) + (lowerIndices * final) + Vector128.Create(rounding)) >> shift; |
|||
Vector128<uint> upper = (((Vector128.Create((uint)last) - upperIndices) * first) + (upperIndices * final) + Vector128.Create(rounding)) >> shift; |
|||
Vector128.Narrow(lower, upper).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < last; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)((((last - i) * first) + (i * final) + rounding) >> shift); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the normal three-tap reference filter to every non-endpoint sample.
|
|||
/// </summary>
|
|||
/// <param name="source">The complete unfiltered reference.</param>
|
|||
/// <param name="destination">The complete filtered reference with its corner already initialized.</param>
|
|||
private static void FilterReferenceThreeTap(ReadOnlySpan<ushort> source, Span<ushort> destination) |
|||
{ |
|||
int last = source.Length - 1; |
|||
destination[last] = source[last]; |
|||
ref ushort sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref ushort destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int i = 1; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector512<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<ushort>.Count) |
|||
{ |
|||
Vector512<ushort> previous = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector512<ushort> current = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector512<ushort> next = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector512<uint> previousLow, Vector512<uint> previousHigh) = Vector512.Widen(previous); |
|||
(Vector512<uint> currentLow, Vector512<uint> currentHigh) = Vector512.Widen(current); |
|||
(Vector512<uint> nextLow, Vector512<uint> nextHigh) = Vector512.Widen(next); |
|||
Vector512<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector512.Create(2U)) >> 2; |
|||
Vector512<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector512.Create(2U)) >> 2; |
|||
Vector512.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector256<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<ushort>.Count) |
|||
{ |
|||
Vector256<ushort> previous = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector256<ushort> current = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector256<ushort> next = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector256<uint> previousLow, Vector256<uint> previousHigh) = Vector256.Widen(previous); |
|||
(Vector256<uint> currentLow, Vector256<uint> currentHigh) = Vector256.Widen(current); |
|||
(Vector256<uint> nextLow, Vector256<uint> nextHigh) = Vector256.Widen(next); |
|||
Vector256<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector256.Create(2U)) >> 2; |
|||
Vector256<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector256.Create(2U)) >> 2; |
|||
Vector256.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = last - Vector128<ushort>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<ushort>.Count) |
|||
{ |
|||
Vector128<ushort> previous = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i - 1)); |
|||
Vector128<ushort> current = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); |
|||
Vector128<ushort> next = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + 1)); |
|||
(Vector128<uint> previousLow, Vector128<uint> previousHigh) = Vector128.Widen(previous); |
|||
(Vector128<uint> currentLow, Vector128<uint> currentHigh) = Vector128.Widen(current); |
|||
(Vector128<uint> nextLow, Vector128<uint> nextHigh) = Vector128.Widen(next); |
|||
Vector128<uint> low = (previousLow + (currentLow << 1) + nextLow + Vector128.Create(2U)) >> 2; |
|||
Vector128<uint> high = (previousHigh + (currentHigh << 1) + nextHigh + Vector128.Create(2U)) >> 2; |
|||
Vector128.Narrow(low, high).StoreUnsafe(ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < last; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = (ushort)((source[i - 1] + (2 * source[i]) + source[i + 1] + 2) >> 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-fifteen lane indices used by 512-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector512<uint> CreateIndicesVector512() |
|||
=> Vector512.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 11U, 12U, 13U, 14U, 15U); |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-seven lane indices used by 256-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector256<uint> CreateIndicesVector256() => Vector256.Create(0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U); |
|||
|
|||
/// <summary>
|
|||
/// Creates the zero-through-three lane indices used by 128-bit weighted interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The ordered lane indices.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector128<uint> CreateIndicesVector128() => Vector128.Create(0U, 1U, 2U, 3U); |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
// 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 representative HEVC intra-prediction modes.
|
|||
/// </summary>
|
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
public class HevcIntraPredictionBenchmarks |
|||
{ |
|||
/// <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 prediction-block side.
|
|||
/// </summary>
|
|||
private const int BlockLog2 = 5; |
|||
|
|||
/// <summary>
|
|||
/// The prediction-block side in samples.
|
|||
/// </summary>
|
|||
private const int BlockSize = 1 << BlockLog2; |
|||
|
|||
/// <summary>
|
|||
/// The prepared top reference shared by deterministic benchmark blocks.
|
|||
/// </summary>
|
|||
private readonly ushort[] top = new ushort[(BlockSize * 2) + 1]; |
|||
|
|||
/// <summary>
|
|||
/// The prepared left reference shared by deterministic benchmark blocks.
|
|||
/// </summary>
|
|||
private readonly ushort[] left = new ushort[(BlockSize * 2) + 1]; |
|||
|
|||
/// <summary>
|
|||
/// The frame-wide reconstructed prediction samples.
|
|||
/// </summary>
|
|||
private readonly ushort[] destination = new ushort[Width * Height]; |
|||
|
|||
/// <summary>
|
|||
/// The maximum-block scratch reused throughout each coded frame.
|
|||
/// </summary>
|
|||
private readonly ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(BlockLog2)]; |
|||
|
|||
/// <summary>
|
|||
/// Populates deterministic twelve-bit reference samples outside the measured frame traversal.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.top[0] = this.left[0] = 1365; |
|||
for (int i = 1; i < this.top.Length; i++) |
|||
{ |
|||
this.top[i] = (ushort)((1365 + (37 * i)) & 4095); |
|||
this.left[i] = (ushort)((1365 + (53 * i)) & 4095); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide planar prediction with 512-, 256-, and 128-bit row dispatch where available.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark(Baseline = true)] |
|||
public ushort PredictPlanarFrame() => this.PredictFrame(0); |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide fractional vertical prediction using contiguous SIMD interpolation.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark] |
|||
public ushort PredictVerticalAngularFrame() => this.PredictFrame(30); |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide horizontal prediction including the SIMD block transposition stage.
|
|||
/// </summary>
|
|||
/// <returns>The final reconstructed sample, keeping the frame output observable.</returns>
|
|||
[Benchmark] |
|||
public ushort PredictHorizontalAngularFrame() => this.PredictFrame(2); |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs every maximum-size prediction block in the coded benchmark frame.
|
|||
/// </summary>
|
|||
/// <param name="mode">The HEVC intra-prediction mode.</param>
|
|||
/// <returns>The final reconstructed sample.</returns>
|
|||
private ushort PredictFrame(int mode) |
|||
{ |
|||
for (int y = 0; y < Height; y += BlockSize) |
|||
{ |
|||
for (int x = 0; x < Width; x += BlockSize) |
|||
{ |
|||
HevcIntraPredictor.Predict( |
|||
this.top, |
|||
this.left, |
|||
this.destination.AsSpan((y * Width) + x), |
|||
Width, |
|||
BlockLog2, |
|||
mode, |
|||
12, |
|||
true, |
|||
this.scratch); |
|||
} |
|||
} |
|||
|
|||
return this.destination[^1]; |
|||
} |
|||
} |
|||
@ -0,0 +1,396 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Hevc; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; |
|||
|
|||
/// <summary>
|
|||
/// Verifies HEVC planar, DC, angular, reference-filter, and SIMD prediction behavior.
|
|||
/// </summary>
|
|||
[Trait("Format", "Heic")] |
|||
public class HevcIntraPredictorTests |
|||
{ |
|||
/// <summary>
|
|||
/// Verifies fixed four-by-four prediction results derived from the HEVC intra-prediction equations.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void PredictsKnownFourByFourBlocks() |
|||
{ |
|||
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
|||
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
|||
int[] modes = [0, 1, 2, 9, 18, 30, 34]; |
|||
ushort[][] expected = |
|||
[ |
|||
[83, 97, 110, 123, 87, 97, 108, 118, 90, 98, 105, 113, 93, 98, 103, 108], |
|||
[84, 93, 97, 101, 88, 92, 92, 92, 90, 92, 92, 92, 91, 92, 92, 92], |
|||
[76, 82, 88, 94, 82, 88, 94, 100, 88, 94, 100, 106, 94, 100, 106, 112], |
|||
[70, 71, 71, 72, 76, 77, 77, 78, 82, 83, 83, 84, 88, 89, 89, 90], |
|||
[64, 80, 96, 112, 70, 64, 80, 96, 76, 70, 64, 80, 82, 76, 70, 64], |
|||
[87, 103, 119, 135, 93, 109, 125, 141, 100, 116, 132, 148, 106, 122, 138, 154], |
|||
[96, 112, 128, 144, 112, 128, 144, 160, 128, 144, 160, 176, 144, 160, 176, 192] |
|||
]; |
|||
|
|||
const int size = 4; |
|||
const int stride = 7; |
|||
ushort[] destination = new ushort[stride * size]; |
|||
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(2)]; |
|||
for (int caseIndex = 0; caseIndex < modes.Length; caseIndex++) |
|||
{ |
|||
destination.AsSpan().Fill(ushort.MaxValue); |
|||
int mode = modes[caseIndex]; |
|||
HevcIntraPredictor.Predict(top, left, destination, stride, 2, mode, 8, mode == 1, scratch); |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
ReadOnlySpan<ushort> expectedRow = expected[caseIndex].AsSpan(y * size, size); |
|||
ReadOnlySpan<ushort> actualRow = destination.AsSpan(y * stride, size); |
|||
Assert.True(expectedRow.SequenceEqual(actualRow), $"Mode {mode}, row {y} did not match the fixed HEVC result."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies the optional luma boundary filter for the pure horizontal and vertical modes.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void FiltersPureDirectionPredictionEdges() |
|||
{ |
|||
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
|||
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
|||
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(2)]; |
|||
ushort[] horizontal = new ushort[16]; |
|||
ushort[] vertical = new ushort[16]; |
|||
|
|||
HevcIntraPredictor.Predict(top, left, horizontal, 4, 2, 10, 8, true, scratch); |
|||
HevcIntraPredictor.Predict(top, left, vertical, 4, 2, 26, 8, true, scratch); |
|||
|
|||
ushort[] expectedHorizontal = [78, 86, 94, 102, 76, 76, 76, 76, 82, 82, 82, 82, 88, 88, 88, 88]; |
|||
ushort[] expectedVertical = [83, 96, 112, 128, 86, 96, 112, 128, 89, 96, 112, 128, 92, 96, 112, 128]; |
|||
Assert.True(expectedHorizontal.AsSpan().SequenceEqual(horizontal)); |
|||
Assert.True(expectedVertical.AsSpan().SequenceEqual(vertical)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies exact three-tap filtering, including the shared top-left sample.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void FiltersReferenceSamplesWithThreeTapKernel() |
|||
{ |
|||
ushort[] top = [64, 80, 96, 112, 128, 144, 160, 176, 192]; |
|||
ushort[] left = [64, 70, 76, 82, 88, 94, 100, 106, 112]; |
|||
ushort[] filteredTop = new ushort[top.Length]; |
|||
ushort[] filteredLeft = new ushort[left.Length]; |
|||
|
|||
HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 2, 8, true); |
|||
|
|||
ushort[] expectedTop = [70, 80, 96, 112, 128, 144, 160, 176, 192]; |
|||
ushort[] expectedLeft = [70, 70, 76, 82, 88, 94, 100, 106, 112]; |
|||
Assert.True(expectedTop.AsSpan().SequenceEqual(filteredTop)); |
|||
Assert.True(expectedLeft.AsSpan().SequenceEqual(filteredLeft)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that eligible thirty-two-sample references use strong bilinear smoothing rather than local three-tap filtering.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void StrongSmoothingReplacesEligibleNonlinearReferences() |
|||
{ |
|||
const int size = 32; |
|||
ushort[] top = new ushort[(size * 2) + 1]; |
|||
ushort[] left = new ushort[top.Length]; |
|||
ushort[] filteredTop = new ushort[top.Length]; |
|||
ushort[] filteredLeft = new ushort[left.Length]; |
|||
for (int i = 0; i < top.Length; i++) |
|||
{ |
|||
top[i] = (ushort)(100 + i + (i % 3)); |
|||
left[i] = (ushort)(100 + (2 * i) + (i % 5)); |
|||
} |
|||
|
|||
// Strong smoothing is selected from the endpoint/midpoint test, so keep those six values exactly bilinear
|
|||
// while the remaining samples deliberately differ from the expected straight lines.
|
|||
top[0] = left[0] = 100; |
|||
top[size] = 132; |
|||
top[size * 2] = 164; |
|||
left[size] = 164; |
|||
left[size * 2] = 228; |
|||
|
|||
HevcIntraPredictor.FilterReferenceSamples(top, left, filteredTop, filteredLeft, 5, 10, true); |
|||
|
|||
for (int i = 0; i < top.Length; i++) |
|||
{ |
|||
Assert.Equal((ushort)(100 + i), filteredTop[i]); |
|||
Assert.Equal((ushort)(100 + (2 * i)), filteredLeft[i]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares every prediction mode and block width with a specification-shaped scalar oracle.
|
|||
/// </summary>
|
|||
/// <param name="log2Size">The base-two logarithm of the tested block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
[Theory] |
|||
[InlineData(2, 8)] |
|||
[InlineData(3, 10)] |
|||
[InlineData(4, 12)] |
|||
[InlineData(5, 12)] |
|||
public void EveryModeMatchesScalarOracle(int log2Size, int bitDepth) |
|||
{ |
|||
int size = 1 << log2Size; |
|||
int maximum = (1 << bitDepth) - 1; |
|||
int referenceLength = (size * 2) + 1; |
|||
ushort[] top = new ushort[referenceLength]; |
|||
ushort[] left = new ushort[referenceLength]; |
|||
top[0] = left[0] = (ushort)(maximum / 3); |
|||
for (int i = 1; i < referenceLength; i++) |
|||
{ |
|||
top[i] = (ushort)((top[0] + (37 * i) + (3 * size)) & maximum); |
|||
left[i] = (ushort)((left[0] + (53 * i) + (5 * size)) & maximum); |
|||
} |
|||
|
|||
int stride = size + 3; |
|||
ushort[] expected = new ushort[stride * size]; |
|||
ushort[] actual = new ushort[stride * size]; |
|||
ushort[] scratch = new ushort[HevcIntraPredictor.GetScratchLength(log2Size)]; |
|||
for (int mode = 0; mode <= 34; mode++) |
|||
{ |
|||
expected.AsSpan().Clear(); |
|||
actual.AsSpan().Clear(); |
|||
PredictScalar(top, left, expected, stride, size, mode, bitDepth, true); |
|||
HevcIntraPredictor.Predict(top, left, actual, stride, log2Size, mode, bitDepth, true, scratch); |
|||
Assert.True(expected.AsSpan().SequenceEqual(actual), $"Mode {mode}, size {size}, and bit depth {bitDepth} did not match the scalar oracle."); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one block directly from the HEVC planar, DC, and angular prediction equations.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="mode">The prediction mode.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
private static void PredictScalar( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
if (mode == 0) |
|||
{ |
|||
int shift = BitOperations.Log2((uint)size) + 1; |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
int horizontal = ((size - x - 1) * left[y + 1]) + ((x + 1) * top[size]); |
|||
int vertical = ((size - y - 1) * top[x + 1]) + ((y + 1) * left[size]); |
|||
destination[(y * destinationStride) + x] = (ushort)((horizontal + vertical + size) >> shift); |
|||
} |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
if (mode == 1) |
|||
{ |
|||
PredictDcScalar(top, left, destination, destinationStride, size, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
if (mode == 10) |
|||
{ |
|||
PredictHorizontalScalar(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
if (mode == 26) |
|||
{ |
|||
PredictVerticalScalar(top, left, destination, destinationStride, size, bitDepth, filterPredictionEdges); |
|||
return; |
|||
} |
|||
|
|||
PredictAngularScalar(top, left, destination, destinationStride, size, mode); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a scalar DC block and its optional boundary filter.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
private static void PredictDcScalar( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
int sum = 0; |
|||
for (int i = 1; i <= size; i++) |
|||
{ |
|||
sum += top[i] + left[i]; |
|||
} |
|||
|
|||
ushort dc = (ushort)((sum + size) >> (BitOperations.Log2((uint)size) + 1)); |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(dc); |
|||
} |
|||
|
|||
if (!filterPredictionEdges) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
destination[0] = (ushort)((top[1] + left[1] + (2 * dc) + 2) >> 2); |
|||
for (int i = 1; i < size; i++) |
|||
{ |
|||
destination[i] = (ushort)((top[i + 1] + (3 * dc) + 2) >> 2); |
|||
destination[i * destinationStride] = (ushort)((left[i + 1] + (3 * dc) + 2) >> 2); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs scalar horizontal prediction and its optional boundary filter.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
private static void PredictHorizontalScalar( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
destination.Slice(y * destinationStride, size).Fill(left[y + 1]); |
|||
} |
|||
|
|||
if (filterPredictionEdges) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
destination[x] = (ushort)Math.Clamp(destination[x] + ((top[x + 1] - top[0]) >> 1), 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs scalar vertical prediction and its optional boundary filter.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="bitDepth">The reconstructed component precision.</param>
|
|||
/// <param name="filterPredictionEdges">Whether the luma edge filter applies.</param>
|
|||
private static void PredictVerticalScalar( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int bitDepth, |
|||
bool filterPredictionEdges) |
|||
{ |
|||
int maximum = (1 << bitDepth) - 1; |
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
top.Slice(1, size).CopyTo(destination[(y * destinationStride)..]); |
|||
if (filterPredictionEdges) |
|||
{ |
|||
int offset = y * destinationStride; |
|||
destination[offset] = (ushort)Math.Clamp(destination[offset] + ((left[y + 1] - left[0]) >> 1), 0, maximum); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs a scalar angular block, including negative-reference extension and horizontal transposition.
|
|||
/// </summary>
|
|||
/// <param name="top">The top reference samples.</param>
|
|||
/// <param name="left">The left reference samples.</param>
|
|||
/// <param name="destination">The destination block origin.</param>
|
|||
/// <param name="destinationStride">The destination row stride.</param>
|
|||
/// <param name="size">The square block side.</param>
|
|||
/// <param name="mode">The angular prediction mode.</param>
|
|||
private static void PredictAngularScalar( |
|||
ReadOnlySpan<ushort> top, |
|||
ReadOnlySpan<ushort> left, |
|||
Span<ushort> destination, |
|||
int destinationStride, |
|||
int size, |
|||
int mode) |
|||
{ |
|||
ReadOnlySpan<int> angles = [0, 2, 5, 9, 13, 17, 21, 26, 32]; |
|||
ReadOnlySpan<int> inverseAngles = [0, 4096, 1638, 910, 630, 482, 390, 315, 256]; |
|||
bool vertical = mode >= 18; |
|||
int angleMode = vertical ? mode - 26 : 10 - mode; |
|||
int absoluteAngleMode = Math.Abs(angleMode); |
|||
int angle = angles[absoluteAngleMode] * Math.Sign(angleMode); |
|||
ReadOnlySpan<ushort> main = vertical ? top : left; |
|||
ReadOnlySpan<ushort> side = vertical ? left : top; |
|||
int mainOrigin = size * 2; |
|||
int[] extendedMain = new int[(4 * size) + 1]; |
|||
for (int i = 0; i < main.Length; i++) |
|||
{ |
|||
extendedMain[mainOrigin + i] = main[i]; |
|||
} |
|||
|
|||
if (angle < 0) |
|||
{ |
|||
int inverseAngleSum = 128; |
|||
for (int index = -1; index > ((size * angle) >> 5); index--) |
|||
{ |
|||
inverseAngleSum += inverseAngles[absoluteAngleMode]; |
|||
extendedMain[mainOrigin + index] = side[inverseAngleSum >> 8]; |
|||
} |
|||
} |
|||
|
|||
ushort[] temporary = new ushort[size * size]; |
|||
for (int y = 0, deltaPosition = angle; y < size; y++, deltaPosition += angle) |
|||
{ |
|||
int deltaInteger = deltaPosition >> 5; |
|||
int deltaFraction = deltaPosition & 31; |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
int index = mainOrigin + x + deltaInteger + 1; |
|||
temporary[(y * size) + x] = deltaFraction == 0 |
|||
? (ushort)extendedMain[index] |
|||
: (ushort)(((extendedMain[index] * (32 - deltaFraction)) + (extendedMain[index + 1] * deltaFraction) + 16) >> 5); |
|||
} |
|||
} |
|||
|
|||
for (int y = 0; y < size; y++) |
|||
{ |
|||
for (int x = 0; x < size; x++) |
|||
{ |
|||
int sourceIndex = vertical ? (y * size) + x : (x * size) + y; |
|||
destination[(y * destinationStride) + x] = temporary[sourceIndex]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue