mirror of https://github.com/SixLabors/ImageSharp
42 changed files with 5418 additions and 2936 deletions
@ -1,69 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block with the sample-domain midpoint when neither top nor left neighbors are available.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1DcFillPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of destination rows.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcFillPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1DcFillPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)blockSize.Width; |
|
||||
this.blockHeight = (uint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcFillPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1DcFillPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (uint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block with the 8-bit midpoint value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
|
|
||||
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1DcFillPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
// With no reference edge, AV1 uses the midpoint of the unsigned 8-bit sample domain as the DC predictor.
|
|
||||
const byte expectedDc = 0x80; |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
for (uint r = 0; r < this.blockHeight; r++) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,245 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs AV1 DC intra-prediction blocks from the available neighboring samples.
|
||||
|
/// </summary>
|
||||
|
internal static class Av1DcIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit DC block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
||||
|
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public static void Predict(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
||||
|
{ |
||||
|
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
||||
|
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
||||
|
|
||||
|
// Section 7.11.2.2 defines the unsigned midpoint when no reference is available. Otherwise adding half
|
||||
|
// the reference count implements the specified rounded mean before extending it across the whole block.
|
||||
|
byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
destination.Slice(row * destinationStride, width).Fill(prediction); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth DC block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
||||
|
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
||||
|
public static void Predict(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
||||
|
{ |
||||
|
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
||||
|
int sum = (hasAbove ? Sum(above[..width]) : 0) + (hasLeft ? Sum(left[..height]) : 0); |
||||
|
short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
destination.Slice(row * destinationStride, width).Fill(prediction); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit DC block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
||||
|
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
||||
|
{ |
||||
|
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
||||
|
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
||||
|
byte prediction = count == 0 ? (byte)128 : (byte)((sum + (count >> 1)) / count); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
ref byte destinationRow = ref destination[row * destinationStride]; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRow, column) = prediction; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth DC block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="hasLeft">Whether the prepared left reference is available.</param>
|
||||
|
/// <param name="hasAbove">Whether the prepared top reference is available.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
||||
|
public static void PredictScalar(bool hasLeft, bool hasAbove, Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth) |
||||
|
{ |
||||
|
int count = (hasAbove ? width : 0) + (hasLeft ? height : 0); |
||||
|
int sum = (hasAbove ? SumScalar(above[..width]) : 0) + (hasLeft ? SumScalar(left[..height]) : 0); |
||||
|
short prediction = count == 0 ? (short)(1 << (bitDepth - 1)) : (short)((sum + (count >> 1)) / count); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
ref short destinationRow = ref destination[row * destinationStride]; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRow, column) = prediction; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sums 8-bit neighboring samples using the widest available SIMD width.
|
||||
|
/// </summary>
|
||||
|
/// <param name="samples">The samples to sum.</param>
|
||||
|
/// <returns>The exact sum.</returns>
|
||||
|
private static int Sum(ReadOnlySpan<byte> samples) |
||||
|
{ |
||||
|
ref byte samplesBase = ref MemoryMarshal.GetReference(samples); |
||||
|
int sum = 0; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector512<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count) |
||||
|
{ |
||||
|
(Vector512<ushort> low, Vector512<ushort> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector512.Sum(low) + Vector512.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector256<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count) |
||||
|
{ |
||||
|
(Vector256<ushort> low, Vector256<ushort> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector256.Sum(low) + Vector256.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector128<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count) |
||||
|
{ |
||||
|
(Vector128<ushort> low, Vector128<ushort> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector128.Sum(low) + Vector128.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < samples.Length; index++) |
||||
|
{ |
||||
|
sum += Unsafe.Add(ref samplesBase, index); |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sums high-bit-depth neighboring samples using the widest available SIMD width.
|
||||
|
/// </summary>
|
||||
|
/// <param name="samples">The samples to sum.</param>
|
||||
|
/// <returns>The exact sum.</returns>
|
||||
|
private static int Sum(ReadOnlySpan<short> samples) |
||||
|
{ |
||||
|
ref short samplesBase = ref MemoryMarshal.GetReference(samples); |
||||
|
int sum = 0; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector512<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count) |
||||
|
{ |
||||
|
(Vector512<int> low, Vector512<int> high) = Vector512.Widen(Vector512.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector512.Sum(low) + Vector512.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector256<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count) |
||||
|
{ |
||||
|
(Vector256<int> low, Vector256<int> high) = Vector256.Widen(Vector256.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector256.Sum(low) + Vector256.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = samples.Length - Vector128<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count) |
||||
|
{ |
||||
|
(Vector128<int> low, Vector128<int> high) = Vector128.Widen(Vector128.LoadUnsafe(ref samplesBase, (nuint)index)); |
||||
|
sum += Vector128.Sum(low) + Vector128.Sum(high); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < samples.Length; index++) |
||||
|
{ |
||||
|
sum += Unsafe.Add(ref samplesBase, index); |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sums 8-bit neighboring samples without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="samples">The samples to sum.</param>
|
||||
|
/// <returns>The exact sum.</returns>
|
||||
|
private static int SumScalar(ReadOnlySpan<byte> samples) |
||||
|
{ |
||||
|
int sum = 0; |
||||
|
foreach (byte sample in samples) |
||||
|
{ |
||||
|
sum += sample; |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sums high-bit-depth neighboring samples without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="samples">The samples to sum.</param>
|
||||
|
/// <returns>The exact sum.</returns>
|
||||
|
private static int SumScalar(ReadOnlySpan<short> samples) |
||||
|
{ |
||||
|
int sum = 0; |
||||
|
foreach (short sample in samples) |
||||
|
{ |
||||
|
sum += sample; |
||||
|
} |
||||
|
|
||||
|
return sum; |
||||
|
} |
||||
|
} |
||||
@ -1,77 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block from the rounded average of its available left neighboring samples.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1DcLeftPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples averaged and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcLeftPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1DcLeftPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)blockSize.Width; |
|
||||
this.blockHeight = (uint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcLeftPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1DcLeftPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (uint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block from its left neighboring samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1DcLeftPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
int sum = 0; |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
for (uint i = 0; i < this.blockHeight; i++) |
|
||||
{ |
|
||||
sum += Unsafe.Add(ref leftRef, i); |
|
||||
} |
|
||||
|
|
||||
// Adding half the sample count implements the normative nearest-integer DC average before division.
|
|
||||
byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight); |
|
||||
for (uint r = 0; r < this.blockHeight; r++) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,86 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block from the rounded average of its top and left neighboring samples.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1DcPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of top samples averaged and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples averaged and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1DcPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1DcPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block from its top and left neighboring samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
int sum = 0; |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
uint count = (uint)(this.blockWidth + this.blockHeight); |
|
||||
uint width = (uint)this.blockWidth; |
|
||||
for (nuint i = 0; i < this.blockWidth; i++) |
|
||||
{ |
|
||||
sum += Unsafe.Add(ref aboveRef, i); |
|
||||
} |
|
||||
|
|
||||
for (nuint i = 0; i < this.blockHeight; i++) |
|
||||
{ |
|
||||
sum += Unsafe.Add(ref leftRef, i); |
|
||||
} |
|
||||
|
|
||||
// Adding half the combined edge count implements the normative nearest-integer DC average before division.
|
|
||||
byte expectedDc = (byte)((sum + (count >> 1)) / count); |
|
||||
for (nuint r = 0; r < this.blockHeight; r++) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, expectedDc, width); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,77 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block from the rounded average of its available top neighboring samples.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1DcTopPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of top samples averaged and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of destination rows.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcTopPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1DcTopPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)blockSize.Width; |
|
||||
this.blockHeight = (uint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DcTopPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1DcTopPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (uint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block from its top neighboring samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples.</param>
|
|
||||
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1DcTopPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
int sum = 0; |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
for (uint i = 0; i < this.blockWidth; i++) |
|
||||
{ |
|
||||
sum += Unsafe.Add(ref aboveRef, i); |
|
||||
} |
|
||||
|
|
||||
// Adding half the sample count implements the normative nearest-integer DC average before division.
|
|
||||
byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth); |
|
||||
for (uint r = 0; r < this.blockHeight; r++) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,619 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides the SIMD projection kernels for AV1 directional intra prediction.
|
||||
|
/// </content>
|
||||
|
internal static partial class Av1DirectionalIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the indices of even bytes in one upsampled reference vector.
|
||||
|
/// </summary>
|
||||
|
private static Vector128<byte> EvenByteIndices => Vector128.Create((byte)0, 2, 4, 6, 8, 10, 12, 14, 0, 2, 4, 6, 8, 10, 12, 14); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the indices of odd bytes in one upsampled reference vector.
|
||||
|
/// </summary>
|
||||
|
private static Vector128<byte> OddByteIndices => Vector128.Create((byte)1, 3, 5, 7, 9, 11, 13, 15, 1, 3, 5, 7, 9, 11, 13, 15); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the indices of even high-bit-depth samples in one upsampled reference vector.
|
||||
|
/// </summary>
|
||||
|
private static Vector128<short> EvenShortIndices => Vector128.Create((short)0, 2, 4, 6, 0, 2, 4, 6); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the indices of odd high-bit-depth samples in one upsampled reference vector.
|
||||
|
/// </summary>
|
||||
|
private static Vector128<short> OddShortIndices => Vector128.Create((short)1, 3, 5, 7, 1, 3, 5, 7); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit zone 1 block using contiguous SIMD projection rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone1(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int projection = derivative; |
||||
|
|
||||
|
for (int row = 0; row < height; row++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
InterpolateRow(destination.Slice(row * destinationStride, width), above, basis, weight, upsample, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth zone 1 block using contiguous SIMD projection rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone1(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int projection = derivative; |
||||
|
|
||||
|
for (int row = 0; row < height; row++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
InterpolateRow(destination.Slice(row * destinationStride, width), above, basis, weight, upsample, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit zone 2 block using vectorized left gathers and contiguous top projection rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
||||
|
/// <param name="dx">The horizontal Q8 derivative.</param>
|
||||
|
/// <param name="dy">The vertical Q8 derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone2(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
||||
|
{ |
||||
|
int aboveShift = upsampleAbove ? 1 : 0; |
||||
|
int minimumTopBasis = -(1 << aboveShift); |
||||
|
int topFractionBits = 6 - aboveShift; |
||||
|
int topBasisIncrement = 1 << aboveShift; |
||||
|
int topProjection = -dx; |
||||
|
|
||||
|
for (int row = 0; row < height; row++, topProjection -= dx) |
||||
|
{ |
||||
|
int topBasis = topProjection >> topFractionBits; |
||||
|
int leftCount = 0; |
||||
|
while (leftCount < width && topBasis < minimumTopBasis) |
||||
|
{ |
||||
|
leftCount++; |
||||
|
topBasis += topBasisIncrement; |
||||
|
} |
||||
|
|
||||
|
Span<byte> destinationRow = destination.Slice(row * destinationStride, width); |
||||
|
int leftProjection = (row << 6) - dy; |
||||
|
InterpolateLeft(destinationRow[..leftCount], left, leftProjection, dy, upsampleLeft); |
||||
|
|
||||
|
if (leftCount < width) |
||||
|
{ |
||||
|
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
||||
|
InterpolateRow(destinationRow[leftCount..], above, topBasis, topWeight, upsampleAbove, int.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth zone 2 block using vectorized left gathers and contiguous top projection rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
||||
|
/// <param name="dx">The horizontal Q8 derivative.</param>
|
||||
|
/// <param name="dy">The vertical Q8 derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone2(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
||||
|
{ |
||||
|
int aboveShift = upsampleAbove ? 1 : 0; |
||||
|
int minimumTopBasis = -(1 << aboveShift); |
||||
|
int topFractionBits = 6 - aboveShift; |
||||
|
int topBasisIncrement = 1 << aboveShift; |
||||
|
int topProjection = -dx; |
||||
|
|
||||
|
for (int row = 0; row < height; row++, topProjection -= dx) |
||||
|
{ |
||||
|
int topBasis = topProjection >> topFractionBits; |
||||
|
int leftCount = 0; |
||||
|
while (leftCount < width && topBasis < minimumTopBasis) |
||||
|
{ |
||||
|
leftCount++; |
||||
|
topBasis += topBasisIncrement; |
||||
|
} |
||||
|
|
||||
|
Span<short> destinationRow = destination.Slice(row * destinationStride, width); |
||||
|
int leftProjection = (row << 6) - dy; |
||||
|
InterpolateLeft(destinationRow[..leftCount], left, leftProjection, dy, upsampleLeft); |
||||
|
|
||||
|
if (leftCount < width) |
||||
|
{ |
||||
|
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
||||
|
InterpolateRow(destinationRow[leftCount..], above, topBasis, topWeight, upsampleAbove, int.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates one 8-bit projection row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination row.</param>
|
||||
|
/// <param name="reference">The projected reference samples.</param>
|
||||
|
/// <param name="basis">The first integral reference coordinate.</param>
|
||||
|
/// <param name="weight">The right-sample interpolation weight.</param>
|
||||
|
/// <param name="upsample">Whether consecutive output samples advance two reference positions.</param>
|
||||
|
/// <param name="maximumBasis">The final extended reference coordinate, or <see cref="int.MaxValue"/> when the row cannot reach it.</param>
|
||||
|
private static void InterpolateRow(Span<byte> destination, ReadOnlySpan<byte> reference, int basis, int weight, bool upsample, int maximumBasis) |
||||
|
{ |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref byte referenceBase = ref MemoryMarshal.GetReference(reference); |
||||
|
int basisIncrement = upsample ? 2 : 1; |
||||
|
int validCount = maximumBasis == int.MaxValue || basis >= maximumBasis |
||||
|
? maximumBasis == int.MaxValue ? destination.Length : 0 |
||||
|
: Math.Min(destination.Length, ((maximumBasis - 1 - basis) / basisIncrement) + 1); |
||||
|
int index = 0; |
||||
|
|
||||
|
if (!upsample) |
||||
|
{ |
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector512<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector512<byte>.Count) |
||||
|
{ |
||||
|
Vector512<byte> left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector512<byte> right = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector256<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector256<byte> right = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector128<byte>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector128<byte>.Count) |
||||
|
{ |
||||
|
Vector128<byte> left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector128<byte> right = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - 8; |
||||
|
for (; index <= oneVectorFromEnd; index += 8) |
||||
|
{ |
||||
|
Vector128<byte> source = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + (index * 2))); |
||||
|
|
||||
|
// ShuffleNative maps to byte-table lookup on AdvSimd and PSHUFB on x86. Eight output samples are
|
||||
|
// gathered from sixteen half-sample positions without scalar lane construction.
|
||||
|
Vector128<byte> left = Vector128.ShuffleNative(source, EvenByteIndices); |
||||
|
Vector128<byte> right = Vector128.ShuffleNative(source, OddByteIndices); |
||||
|
Vector128<byte> prediction = Interpolate(left, right, weight); |
||||
|
Unsafe.As<byte, ulong>(ref Unsafe.Add(ref destinationBase, index)) = prediction.AsUInt64().GetElement(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - 4; |
||||
|
for (; index <= oneVectorFromEnd; index += 4) |
||||
|
{ |
||||
|
int source = basis + (index * basisIncrement); |
||||
|
Vector128<int> left = Vector128.Create( |
||||
|
(int)Unsafe.Add(ref referenceBase, source), |
||||
|
Unsafe.Add(ref referenceBase, source + basisIncrement), |
||||
|
Unsafe.Add(ref referenceBase, source + (2 * basisIncrement)), |
||||
|
Unsafe.Add(ref referenceBase, source + (3 * basisIncrement))); |
||||
|
|
||||
|
Vector128<int> right = Vector128.Create( |
||||
|
(int)Unsafe.Add(ref referenceBase, source + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + basisIncrement + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + (2 * basisIncrement) + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + (3 * basisIncrement) + 1)); |
||||
|
|
||||
|
StoreFourBytes(Interpolate(left, right, Vector128.Create(weight)), ref Unsafe.Add(ref destinationBase, index)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < validCount; index++) |
||||
|
{ |
||||
|
int source = basis + (index * basisIncrement); |
||||
|
Unsafe.Add(ref destinationBase, index) = (byte)(((Unsafe.Add(ref referenceBase, source) * (32 - weight)) + (Unsafe.Add(ref referenceBase, source + 1) * weight) + 16) >> 5); |
||||
|
} |
||||
|
|
||||
|
if (validCount < destination.Length) |
||||
|
{ |
||||
|
destination[validCount..].Fill(Unsafe.Add(ref referenceBase, maximumBasis)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates one high-bit-depth projection row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination row.</param>
|
||||
|
/// <param name="reference">The projected reference samples.</param>
|
||||
|
/// <param name="basis">The first integral reference coordinate.</param>
|
||||
|
/// <param name="weight">The right-sample interpolation weight.</param>
|
||||
|
/// <param name="upsample">Whether consecutive output samples advance two reference positions.</param>
|
||||
|
/// <param name="maximumBasis">The final extended reference coordinate, or <see cref="int.MaxValue"/> when the row cannot reach it.</param>
|
||||
|
private static void InterpolateRow(Span<short> destination, ReadOnlySpan<short> reference, int basis, int weight, bool upsample, int maximumBasis) |
||||
|
{ |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref short referenceBase = ref MemoryMarshal.GetReference(reference); |
||||
|
int basisIncrement = upsample ? 2 : 1; |
||||
|
int validCount = maximumBasis == int.MaxValue || basis >= maximumBasis |
||||
|
? maximumBasis == int.MaxValue ? destination.Length : 0 |
||||
|
: Math.Min(destination.Length, ((maximumBasis - 1 - basis) / basisIncrement) + 1); |
||||
|
int index = 0; |
||||
|
|
||||
|
if (!upsample) |
||||
|
{ |
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector512<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector512<short>.Count) |
||||
|
{ |
||||
|
Vector512<short> left = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector512<short> right = Vector512.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector256<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector256<short>.Count) |
||||
|
{ |
||||
|
Vector256<short> left = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector256<short> right = Vector256.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - Vector128<short>.Count; |
||||
|
for (; index <= oneVectorFromEnd; index += Vector128<short>.Count) |
||||
|
{ |
||||
|
Vector128<short> left = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index)); |
||||
|
Vector128<short> right = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + index + 1)); |
||||
|
Interpolate(left, right, weight).StoreUnsafe(ref destinationBase, (nuint)index); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - 4; |
||||
|
for (; index <= oneVectorFromEnd; index += 4) |
||||
|
{ |
||||
|
Vector128<short> source = Vector128.LoadUnsafe(ref referenceBase, (nuint)(basis + (index * 2))); |
||||
|
Vector128<short> left = Vector128.ShuffleNative(source, EvenShortIndices); |
||||
|
Vector128<short> right = Vector128.ShuffleNative(source, OddShortIndices); |
||||
|
Vector128<short> prediction = Interpolate(left, right, weight); |
||||
|
Unsafe.As<short, ulong>(ref Unsafe.Add(ref destinationBase, index)) = prediction.AsUInt64().GetElement(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = validCount - 4; |
||||
|
for (; index <= oneVectorFromEnd; index += 4) |
||||
|
{ |
||||
|
int source = basis + (index * basisIncrement); |
||||
|
Vector128<int> left = Vector128.Create( |
||||
|
(int)Unsafe.Add(ref referenceBase, source), |
||||
|
Unsafe.Add(ref referenceBase, source + basisIncrement), |
||||
|
Unsafe.Add(ref referenceBase, source + (2 * basisIncrement)), |
||||
|
Unsafe.Add(ref referenceBase, source + (3 * basisIncrement))); |
||||
|
|
||||
|
Vector128<int> right = Vector128.Create( |
||||
|
(int)Unsafe.Add(ref referenceBase, source + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + basisIncrement + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + (2 * basisIncrement) + 1), |
||||
|
Unsafe.Add(ref referenceBase, source + (3 * basisIncrement) + 1)); |
||||
|
|
||||
|
StoreFourShorts(Interpolate(left, right, Vector128.Create(weight)), ref Unsafe.Add(ref destinationBase, index)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < validCount; index++) |
||||
|
{ |
||||
|
int source = basis + (index * basisIncrement); |
||||
|
Unsafe.Add(ref destinationBase, index) = (short)(((Unsafe.Add(ref referenceBase, source) * (32 - weight)) + (Unsafe.Add(ref referenceBase, source + 1) * weight) + 16) >> 5); |
||||
|
} |
||||
|
|
||||
|
if (validCount < destination.Length) |
||||
|
{ |
||||
|
destination[validCount..].Fill(Unsafe.Add(ref referenceBase, maximumBasis)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates the left-edge prefix of one 8-bit zone 2 row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination prefix.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="projection">The first Q6 left projection.</param>
|
||||
|
/// <param name="derivative">The Q8 derivative subtracted between columns.</param>
|
||||
|
/// <param name="upsample">Whether the left reference contains half-sample positions.</param>
|
||||
|
private static void InterpolateLeft(Span<byte> destination, ReadOnlySpan<byte> left, int projection, int derivative, bool upsample) |
||||
|
{ |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref byte leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = destination.Length - 4; |
||||
|
for (; index <= oneVectorFromEnd; index += 4) |
||||
|
{ |
||||
|
int projection0 = projection - (index * derivative); |
||||
|
int projection1 = projection0 - derivative; |
||||
|
int projection2 = projection1 - derivative; |
||||
|
int projection3 = projection2 - derivative; |
||||
|
int basis0 = projection0 >> fractionBits; |
||||
|
int basis1 = projection1 >> fractionBits; |
||||
|
int basis2 = projection2 >> fractionBits; |
||||
|
int basis3 = projection3 >> fractionBits; |
||||
|
Vector128<int> source0 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0), Unsafe.Add(ref leftBase, basis1), Unsafe.Add(ref leftBase, basis2), Unsafe.Add(ref leftBase, basis3)); |
||||
|
Vector128<int> source1 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0 + 1), Unsafe.Add(ref leftBase, basis1 + 1), Unsafe.Add(ref leftBase, basis2 + 1), Unsafe.Add(ref leftBase, basis3 + 1)); |
||||
|
Vector128<int> weights = Vector128.Create( |
||||
|
((projection0 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection1 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection2 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection3 << upsampleShift) & 0x3F) >> 1); |
||||
|
|
||||
|
StoreFourBytes(Interpolate(source0, source1, weights), ref Unsafe.Add(ref destinationBase, index)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < destination.Length; index++) |
||||
|
{ |
||||
|
int currentProjection = projection - (index * derivative); |
||||
|
int basis = currentProjection >> fractionBits; |
||||
|
int weight = ((currentProjection << upsampleShift) & 0x3F) >> 1; |
||||
|
Unsafe.Add(ref destinationBase, index) = (byte)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates the left-edge prefix of one high-bit-depth zone 2 row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination prefix.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="projection">The first Q6 left projection.</param>
|
||||
|
/// <param name="derivative">The Q8 derivative subtracted between columns.</param>
|
||||
|
/// <param name="upsample">Whether the left reference contains half-sample positions.</param>
|
||||
|
private static void InterpolateLeft(Span<short> destination, ReadOnlySpan<short> left, int projection, int derivative, bool upsample) |
||||
|
{ |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref short leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int index = 0; |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int oneVectorFromEnd = destination.Length - 4; |
||||
|
for (; index <= oneVectorFromEnd; index += 4) |
||||
|
{ |
||||
|
int projection0 = projection - (index * derivative); |
||||
|
int projection1 = projection0 - derivative; |
||||
|
int projection2 = projection1 - derivative; |
||||
|
int projection3 = projection2 - derivative; |
||||
|
int basis0 = projection0 >> fractionBits; |
||||
|
int basis1 = projection1 >> fractionBits; |
||||
|
int basis2 = projection2 >> fractionBits; |
||||
|
int basis3 = projection3 >> fractionBits; |
||||
|
Vector128<int> source0 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0), Unsafe.Add(ref leftBase, basis1), Unsafe.Add(ref leftBase, basis2), Unsafe.Add(ref leftBase, basis3)); |
||||
|
Vector128<int> source1 = Vector128.Create((int)Unsafe.Add(ref leftBase, basis0 + 1), Unsafe.Add(ref leftBase, basis1 + 1), Unsafe.Add(ref leftBase, basis2 + 1), Unsafe.Add(ref leftBase, basis3 + 1)); |
||||
|
Vector128<int> weights = Vector128.Create( |
||||
|
((projection0 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection1 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection2 << upsampleShift) & 0x3F) >> 1, |
||||
|
((projection3 << upsampleShift) & 0x3F) >> 1); |
||||
|
|
||||
|
StoreFourShorts(Interpolate(source0, source1, weights), ref Unsafe.Add(ref destinationBase, index)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (; index < destination.Length; index++) |
||||
|
{ |
||||
|
int currentProjection = projection - (index * derivative); |
||||
|
int basis = currentProjection >> fractionBits; |
||||
|
int weight = ((currentProjection << upsampleShift) & 0x3F) >> 1; |
||||
|
Unsafe.Add(ref destinationBase, index) = (short)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates sixty-four pairs of 8-bit references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<byte> Interpolate(Vector512<byte> left, Vector512<byte> right, int weight) |
||||
|
{ |
||||
|
(Vector512<ushort> leftLow, Vector512<ushort> leftHigh) = Vector512.Widen(left); |
||||
|
(Vector512<ushort> rightLow, Vector512<ushort> rightHigh) = Vector512.Widen(right); |
||||
|
Vector512<ushort> rounding = Vector512.Create((ushort)16); |
||||
|
Vector512<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
||||
|
Vector512<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
||||
|
return Vector512.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates thirty-two pairs of 8-bit references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<byte> Interpolate(Vector256<byte> left, Vector256<byte> right, int weight) |
||||
|
{ |
||||
|
(Vector256<ushort> leftLow, Vector256<ushort> leftHigh) = Vector256.Widen(left); |
||||
|
(Vector256<ushort> rightLow, Vector256<ushort> rightHigh) = Vector256.Widen(right); |
||||
|
Vector256<ushort> rounding = Vector256.Create((ushort)16); |
||||
|
Vector256<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
||||
|
Vector256<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
||||
|
return Vector256.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates sixteen pairs of 8-bit references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<byte> Interpolate(Vector128<byte> left, Vector128<byte> right, int weight) |
||||
|
{ |
||||
|
(Vector128<ushort> leftLow, Vector128<ushort> leftHigh) = Vector128.Widen(left); |
||||
|
(Vector128<ushort> rightLow, Vector128<ushort> rightHigh) = Vector128.Widen(right); |
||||
|
Vector128<ushort> rounding = Vector128.Create((ushort)16); |
||||
|
Vector128<ushort> low = ((leftLow * (ushort)(32 - weight)) + (rightLow * (ushort)weight) + rounding) >> 5; |
||||
|
Vector128<ushort> high = ((leftHigh * (ushort)(32 - weight)) + (rightHigh * (ushort)weight) + rounding) >> 5; |
||||
|
return Vector128.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates thirty-two pairs of high-bit-depth references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<short> Interpolate(Vector512<short> left, Vector512<short> right, int weight) |
||||
|
{ |
||||
|
(Vector512<int> leftLow, Vector512<int> leftHigh) = Vector512.Widen(left); |
||||
|
(Vector512<int> rightLow, Vector512<int> rightHigh) = Vector512.Widen(right); |
||||
|
Vector512<int> rounding = Vector512.Create(16); |
||||
|
Vector512<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
||||
|
Vector512<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
||||
|
return Vector512.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates sixteen pairs of high-bit-depth references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<short> Interpolate(Vector256<short> left, Vector256<short> right, int weight) |
||||
|
{ |
||||
|
(Vector256<int> leftLow, Vector256<int> leftHigh) = Vector256.Widen(left); |
||||
|
(Vector256<int> rightLow, Vector256<int> rightHigh) = Vector256.Widen(right); |
||||
|
Vector256<int> rounding = Vector256.Create(16); |
||||
|
Vector256<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
||||
|
Vector256<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
||||
|
return Vector256.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates eight pairs of high-bit-depth references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weight">The right-sample weight.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<short> Interpolate(Vector128<short> left, Vector128<short> right, int weight) |
||||
|
{ |
||||
|
(Vector128<int> leftLow, Vector128<int> leftHigh) = Vector128.Widen(left); |
||||
|
(Vector128<int> rightLow, Vector128<int> rightHigh) = Vector128.Widen(right); |
||||
|
Vector128<int> rounding = Vector128.Create(16); |
||||
|
Vector128<int> low = ((leftLow * (32 - weight)) + (rightLow * weight) + rounding) >> 5; |
||||
|
Vector128<int> high = ((leftHigh * (32 - weight)) + (rightHigh * weight) + rounding) >> 5; |
||||
|
return Vector128.Narrow(low, high); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interpolates four widened reference pairs with independent weights.
|
||||
|
/// </summary>
|
||||
|
/// <param name="left">The left interpolation samples.</param>
|
||||
|
/// <param name="right">The right interpolation samples.</param>
|
||||
|
/// <param name="weights">The right-sample weights.</param>
|
||||
|
/// <returns>The rounded interpolated samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Interpolate(Vector128<int> left, Vector128<int> right, Vector128<int> weights) |
||||
|
=> ((left * (Vector128.Create(32) - weights)) + (right * weights) + Vector128.Create(16)) >> 5; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four widened predictions as packed 8-bit samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The widened predictions.</param>
|
||||
|
/// <param name="destination">The first destination sample.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourBytes(Vector128<int> prediction, ref byte destination) |
||||
|
{ |
||||
|
Vector128<ushort> narrowed16 = Vector128.Narrow(prediction.AsUInt32(), Vector128<uint>.Zero); |
||||
|
Vector128<byte> narrowed8 = Vector128.Narrow(narrowed16, Vector128<ushort>.Zero); |
||||
|
Unsafe.As<byte, uint>(ref destination) = narrowed8.AsUInt32().GetElement(0); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four widened predictions as packed high-bit-depth samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The widened predictions.</param>
|
||||
|
/// <param name="destination">The first destination sample.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourShorts(Vector128<int> prediction, ref short destination) |
||||
|
{ |
||||
|
Vector128<short> narrowed = Vector128.Narrow(prediction, Vector128<int>.Zero); |
||||
|
Unsafe.As<short, ulong>(ref destination) = narrowed.AsUInt64().GetElement(0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,313 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides SIMD block transposition for zone 3 directional prediction.
|
||||
|
/// </content>
|
||||
|
internal static partial class Av1DirectionalIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Transposes a contiguous 8-bit prediction block into the reconstructed destination.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceWidth">The source row width.</param>
|
||||
|
/// <param name="sourceHeight">The number of source rows.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
private static void Transpose(ReadOnlySpan<byte> source, Span<byte> destination, int sourceWidth, int sourceHeight, int destinationStride) |
||||
|
{ |
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int tileSize = sourceWidth >= 8 && sourceHeight >= 8 ? 8 : 4; |
||||
|
for (int y = 0; y < sourceHeight; y += tileSize) |
||||
|
{ |
||||
|
for (int x = 0; x < sourceWidth; x += tileSize) |
||||
|
{ |
||||
|
if (tileSize == 8) |
||||
|
{ |
||||
|
Transpose8x8(source, destination, sourceWidth, destinationStride, x, y); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Transpose4x4(source, destination, sourceWidth, destinationStride, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < sourceHeight; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < sourceWidth; x++) |
||||
|
{ |
||||
|
destination[(x * destinationStride) + y] = source[(y * sourceWidth) + x]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes a contiguous high-bit-depth prediction block into the reconstructed destination.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceWidth">The source row width.</param>
|
||||
|
/// <param name="sourceHeight">The number of source rows.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
private static void Transpose(ReadOnlySpan<short> source, Span<short> destination, int sourceWidth, int sourceHeight, int destinationStride) |
||||
|
{ |
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int tileSize = sourceWidth >= 8 && sourceHeight >= 8 ? 8 : 4; |
||||
|
for (int y = 0; y < sourceHeight; y += tileSize) |
||||
|
{ |
||||
|
for (int x = 0; x < sourceWidth; x += tileSize) |
||||
|
{ |
||||
|
if (tileSize == 8) |
||||
|
{ |
||||
|
Transpose8x8(source, destination, sourceWidth, destinationStride, x, y); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Transpose4x4(source, destination, sourceWidth, destinationStride, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (int y = 0; y < sourceHeight; y++) |
||||
|
{ |
||||
|
for (int x = 0; x < sourceWidth; x++) |
||||
|
{ |
||||
|
destination[(x * destinationStride) + y] = source[(y * sourceWidth) + x]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes one eight-by-eight tile of 8-bit prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="x">The tile X coordinate.</param>
|
||||
|
/// <param name="y">The tile Y coordinate.</param>
|
||||
|
private static void Transpose8x8(ReadOnlySpan<byte> source, Span<byte> destination, int sourceStride, int destinationStride, int x, int y) |
||||
|
{ |
||||
|
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
Vector128<byte> row0 = LoadEightBytes(ref sourceBase, ((y + 0) * sourceStride) + x); |
||||
|
Vector128<byte> row1 = LoadEightBytes(ref sourceBase, ((y + 1) * sourceStride) + x); |
||||
|
Vector128<byte> row2 = LoadEightBytes(ref sourceBase, ((y + 2) * sourceStride) + x); |
||||
|
Vector128<byte> row3 = LoadEightBytes(ref sourceBase, ((y + 3) * sourceStride) + x); |
||||
|
Vector128<byte> row4 = LoadEightBytes(ref sourceBase, ((y + 4) * sourceStride) + x); |
||||
|
Vector128<byte> row5 = LoadEightBytes(ref sourceBase, ((y + 5) * sourceStride) + x); |
||||
|
Vector128<byte> row6 = LoadEightBytes(ref sourceBase, ((y + 6) * sourceStride) + x); |
||||
|
Vector128<byte> row7 = LoadEightBytes(ref sourceBase, ((y + 7) * sourceStride) + x); |
||||
|
|
||||
|
// Three unpack stages exchange one, two, then four byte coordinates. Each final vector contains two
|
||||
|
// complete source columns, which are written as two contiguous eight-byte destination rows.
|
||||
|
Vector128<byte> pair0 = Vector128_.UnpackLow(row0, row1); |
||||
|
Vector128<byte> pair2 = Vector128_.UnpackLow(row2, row3); |
||||
|
Vector128<byte> pair4 = Vector128_.UnpackLow(row4, row5); |
||||
|
Vector128<byte> pair6 = Vector128_.UnpackLow(row6, row7); |
||||
|
Vector128<short> quad0 = Vector128_.UnpackLow(pair0.AsInt16(), pair2.AsInt16()); |
||||
|
Vector128<short> quad1 = Vector128_.UnpackHigh(pair0.AsInt16(), pair2.AsInt16()); |
||||
|
Vector128<short> quad4 = Vector128_.UnpackLow(pair4.AsInt16(), pair6.AsInt16()); |
||||
|
Vector128<short> quad5 = Vector128_.UnpackHigh(pair4.AsInt16(), pair6.AsInt16()); |
||||
|
Vector128<int> columns01 = Vector128_.UnpackLow(quad0.AsInt32(), quad4.AsInt32()); |
||||
|
Vector128<int> columns23 = Vector128_.UnpackHigh(quad0.AsInt32(), quad4.AsInt32()); |
||||
|
Vector128<int> columns45 = Vector128_.UnpackLow(quad1.AsInt32(), quad5.AsInt32()); |
||||
|
Vector128<int> columns67 = Vector128_.UnpackHigh(quad1.AsInt32(), quad5.AsInt32()); |
||||
|
|
||||
|
StoreEightBytes(columns01.AsUInt64().GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y); |
||||
|
StoreEightBytes(columns01.AsUInt64().GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y); |
||||
|
StoreEightBytes(columns23.AsUInt64().GetElement(0), ref destinationBase, ((x + 2) * destinationStride) + y); |
||||
|
StoreEightBytes(columns23.AsUInt64().GetElement(1), ref destinationBase, ((x + 3) * destinationStride) + y); |
||||
|
StoreEightBytes(columns45.AsUInt64().GetElement(0), ref destinationBase, ((x + 4) * destinationStride) + y); |
||||
|
StoreEightBytes(columns45.AsUInt64().GetElement(1), ref destinationBase, ((x + 5) * destinationStride) + y); |
||||
|
StoreEightBytes(columns67.AsUInt64().GetElement(0), ref destinationBase, ((x + 6) * destinationStride) + y); |
||||
|
StoreEightBytes(columns67.AsUInt64().GetElement(1), ref destinationBase, ((x + 7) * destinationStride) + y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes one four-by-four tile of 8-bit prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="x">The tile X coordinate.</param>
|
||||
|
/// <param name="y">The tile Y coordinate.</param>
|
||||
|
private static void Transpose4x4(ReadOnlySpan<byte> source, Span<byte> destination, int sourceStride, int destinationStride, int x, int y) |
||||
|
{ |
||||
|
ref byte sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
Vector128<byte> row0 = LoadFourBytes(ref sourceBase, ((y + 0) * sourceStride) + x); |
||||
|
Vector128<byte> row1 = LoadFourBytes(ref sourceBase, ((y + 1) * sourceStride) + x); |
||||
|
Vector128<byte> row2 = LoadFourBytes(ref sourceBase, ((y + 2) * sourceStride) + x); |
||||
|
Vector128<byte> row3 = LoadFourBytes(ref sourceBase, ((y + 3) * sourceStride) + x); |
||||
|
Vector128<byte> pair0 = Vector128_.UnpackLow(row0, row1); |
||||
|
Vector128<byte> pair1 = Vector128_.UnpackLow(row2, row3); |
||||
|
Vector128<short> columns = Vector128_.UnpackLow(pair0.AsInt16(), pair1.AsInt16()); |
||||
|
|
||||
|
Vector128<uint> packedColumns = columns.AsUInt32(); |
||||
|
StoreFourBytes(packedColumns.GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y); |
||||
|
StoreFourBytes(packedColumns.GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y); |
||||
|
StoreFourBytes(packedColumns.GetElement(2), ref destinationBase, ((x + 2) * destinationStride) + y); |
||||
|
StoreFourBytes(packedColumns.GetElement(3), ref destinationBase, ((x + 3) * destinationStride) + y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes one eight-by-eight tile of high-bit-depth prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="x">The tile X coordinate.</param>
|
||||
|
/// <param name="y">The tile Y coordinate.</param>
|
||||
|
private static void Transpose8x8(ReadOnlySpan<short> source, Span<short> destination, int sourceStride, int destinationStride, int x, int y) |
||||
|
{ |
||||
|
ref short sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
Vector128<short> row0 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 0) * sourceStride) + x)); |
||||
|
Vector128<short> row1 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 1) * sourceStride) + x)); |
||||
|
Vector128<short> row2 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 2) * sourceStride) + x)); |
||||
|
Vector128<short> row3 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 3) * sourceStride) + x)); |
||||
|
Vector128<short> row4 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 4) * sourceStride) + x)); |
||||
|
Vector128<short> row5 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 5) * sourceStride) + x)); |
||||
|
Vector128<short> row6 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 6) * sourceStride) + x)); |
||||
|
Vector128<short> row7 = Vector128.LoadUnsafe(ref sourceBase, (nuint)(((y + 7) * sourceStride) + x)); |
||||
|
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<short> column0 = Vector128_.UnpackLow(quad0.AsInt64(), quad4.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column1 = Vector128_.UnpackHigh(quad0.AsInt64(), quad4.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column2 = Vector128_.UnpackLow(quad1.AsInt64(), quad5.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column3 = Vector128_.UnpackHigh(quad1.AsInt64(), quad5.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column4 = Vector128_.UnpackLow(quad2.AsInt64(), quad6.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column5 = Vector128_.UnpackHigh(quad2.AsInt64(), quad6.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column6 = Vector128_.UnpackLow(quad3.AsInt64(), quad7.AsInt64()).AsInt16(); |
||||
|
Vector128<short> column7 = Vector128_.UnpackHigh(quad3.AsInt64(), quad7.AsInt64()).AsInt16(); |
||||
|
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)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Transposes one four-by-four tile of high-bit-depth prediction samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The contiguous source block.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="sourceStride">The source row stride.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="x">The tile X coordinate.</param>
|
||||
|
/// <param name="y">The tile Y coordinate.</param>
|
||||
|
private static void Transpose4x4(ReadOnlySpan<short> source, Span<short> destination, int sourceStride, int destinationStride, int x, int y) |
||||
|
{ |
||||
|
ref short sourceBase = ref MemoryMarshal.GetReference(source); |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
Vector128<short> row0 = LoadFourShorts(ref sourceBase, ((y + 0) * sourceStride) + x); |
||||
|
Vector128<short> row1 = LoadFourShorts(ref sourceBase, ((y + 1) * sourceStride) + x); |
||||
|
Vector128<short> row2 = LoadFourShorts(ref sourceBase, ((y + 2) * sourceStride) + x); |
||||
|
Vector128<short> row3 = LoadFourShorts(ref sourceBase, ((y + 3) * sourceStride) + x); |
||||
|
Vector128<short> pair0 = Vector128_.UnpackLow(row0, row1); |
||||
|
Vector128<short> pair1 = Vector128_.UnpackLow(row2, row3); |
||||
|
Vector128<int> columns01 = Vector128_.UnpackLow(pair0.AsInt32(), pair1.AsInt32()); |
||||
|
Vector128<int> columns23 = Vector128_.UnpackHigh(pair0.AsInt32(), pair1.AsInt32()); |
||||
|
|
||||
|
StoreFourShorts(columns01.AsUInt64().GetElement(0), ref destinationBase, ((x + 0) * destinationStride) + y); |
||||
|
StoreFourShorts(columns01.AsUInt64().GetElement(1), ref destinationBase, ((x + 1) * destinationStride) + y); |
||||
|
StoreFourShorts(columns23.AsUInt64().GetElement(0), ref destinationBase, ((x + 2) * destinationStride) + y); |
||||
|
StoreFourShorts(columns23.AsUInt64().GetElement(1), ref destinationBase, ((x + 3) * destinationStride) + y); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads eight bytes into the lower half of a vector without reading past a tile row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source buffer origin.</param>
|
||||
|
/// <param name="offset">The source offset.</param>
|
||||
|
/// <returns>The loaded bytes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<byte> LoadEightBytes(ref byte source, int offset) |
||||
|
=> Vector128.Create(Unsafe.As<byte, ulong>(ref Unsafe.Add(ref source, offset)), 0UL).AsByte(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads four bytes into the low vector lanes without reading past a tile row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source buffer origin.</param>
|
||||
|
/// <param name="offset">The source offset.</param>
|
||||
|
/// <returns>The loaded bytes.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<byte> LoadFourBytes(ref byte source, int offset) |
||||
|
=> Vector128.Create(Unsafe.As<byte, uint>(ref Unsafe.Add(ref source, offset)), 0U, 0U, 0U).AsByte(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads four high-bit-depth samples into the lower half of a vector without reading past a tile row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source buffer origin.</param>
|
||||
|
/// <param name="offset">The source offset.</param>
|
||||
|
/// <returns>The loaded samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<short> LoadFourShorts(ref short source, int offset) |
||||
|
=> Vector128.Create(Unsafe.As<short, ulong>(ref Unsafe.Add(ref source, offset)), 0UL).AsInt16(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores the lower eight bytes of a vector.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed bytes.</param>
|
||||
|
/// <param name="destination">The destination buffer origin.</param>
|
||||
|
/// <param name="offset">The destination offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreEightBytes(ulong source, ref byte destination, int offset) |
||||
|
=> Unsafe.As<byte, ulong>(ref Unsafe.Add(ref destination, offset)) = source; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four bytes from the low vector lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed bytes.</param>
|
||||
|
/// <param name="destination">The destination buffer origin.</param>
|
||||
|
/// <param name="offset">The destination offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourBytes(uint source, ref byte destination, int offset) |
||||
|
=> Unsafe.As<byte, uint>(ref Unsafe.Add(ref destination, offset)) = source; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four high-bit-depth samples from the low vector lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="destination">The destination buffer origin.</param>
|
||||
|
/// <param name="offset">The destination offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourShorts(ulong source, ref short destination, int offset) |
||||
|
=> Unsafe.As<short, ulong>(ref Unsafe.Add(ref destination, offset)) = source; |
||||
|
} |
||||
@ -0,0 +1,443 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs AV1 directional intra-prediction blocks from prepared neighboring samples.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The three projection zones implement the directional prediction process in section 7.11.2.4 of the AV1 specification.
|
||||
|
/// </remarks>
|
||||
|
internal static partial class Av1DirectionalIntraPredictor |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The largest number of samples required to transpose a directional prediction block.
|
||||
|
/// </summary>
|
||||
|
public const int ScratchLength = 64 * 64; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Q8 directional derivatives indexed by acute prediction angle.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> DirectionalIntraDerivative => |
||||
|
[ |
||||
|
|
||||
|
// Zero entries represent angles which AV1 never signals. Direct indexing avoids a search or division in
|
||||
|
// each directional block while retaining the exact fixed-point projections from the normative table.
|
||||
|
0, 0, 0, 1023, 0, 0, 547, 0, 0, 372, 0, 0, 0, 0, 273, 0, 0, 215, 0, 0, 178, 0, 0, |
||||
|
151, 0, 0, 132, 0, 0, 116, 0, 0, 102, 0, 0, 0, 90, 0, 0, 80, 0, 0, 71, 0, 0, 64, 0, 0, |
||||
|
57, 0, 0, 51, 0, 0, 45, 0, 0, 0, 40, 0, 0, 35, 0, 0, 31, 0, 0, 27, 0, 0, 23, 0, 0, |
||||
|
19, 0, 0, 15, 0, 0, 0, 0, 11, 0, 0, 7, 0, 0, 3, 0, 0, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit directional block using the widest available SIMD path.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="transformSize">The predicted block dimensions.</param>
|
||||
|
/// <param name="above">The prepared top reference, including any required extension.</param>
|
||||
|
/// <param name="left">The prepared left reference, including any required extension.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
/// <param name="scratch">The caller-owned block transposition workspace.</param>
|
||||
|
public static void Predict(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<byte> scratch) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
if (angle is > 0 and < 90) |
||||
|
{ |
||||
|
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 90 and < 180) |
||||
|
{ |
||||
|
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 180 and < 270) |
||||
|
{ |
||||
|
// libaom computes zone 3 as a zone 1 block with swapped dimensions, then transposes it. This preserves
|
||||
|
// contiguous reference reads and destination stores in both hot stages instead of scattering columns.
|
||||
|
Span<byte> transposed = scratch[..(width * height)]; |
||||
|
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width); |
||||
|
Transpose(transposed, destination, height, width, destinationStride); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
||||
|
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth directional block using the widest available SIMD path.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="transformSize">The predicted block dimensions.</param>
|
||||
|
/// <param name="above">The prepared top reference, including any required extension.</param>
|
||||
|
/// <param name="left">The prepared left reference, including any required extension.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
/// <param name="scratch">The caller-owned block transposition workspace.</param>
|
||||
|
public static void Predict(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle, Span<short> scratch) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
if (angle is > 0 and < 90) |
||||
|
{ |
||||
|
PredictZone1(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 90 and < 180) |
||||
|
{ |
||||
|
PredictZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 180 and < 270) |
||||
|
{ |
||||
|
Span<short> transposed = scratch[..(width * height)]; |
||||
|
PredictZone1(transposed, height, left, upsampleLeft, GetDeltaY(angle), height, width); |
||||
|
Transpose(transposed, destination, height, width, destinationStride); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
||||
|
Av1IntraPredictorBase.GetPredictor(mode).Predict(destination, destinationStride, above, left, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit directional block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="transformSize">The predicted block dimensions.</param>
|
||||
|
/// <param name="above">The prepared top reference, including any required extension.</param>
|
||||
|
/// <param name="left">The prepared left reference, including any required extension.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
public static void PredictScalar(Span<byte> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int angle) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
if (angle is > 0 and < 90) |
||||
|
{ |
||||
|
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 90 and < 180) |
||||
|
{ |
||||
|
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 180 and < 270) |
||||
|
{ |
||||
|
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
||||
|
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth directional block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="transformSize">The predicted block dimensions.</param>
|
||||
|
/// <param name="above">The prepared top reference, including any required extension.</param>
|
||||
|
/// <param name="left">The prepared left reference, including any required extension.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top edge contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left edge contains half-sample positions.</param>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
public static void PredictScalar(Span<short> destination, int destinationStride, Av1TransformSize transformSize, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int angle) |
||||
|
{ |
||||
|
int width = transformSize.GetWidth(); |
||||
|
int height = transformSize.GetHeight(); |
||||
|
|
||||
|
if (angle is > 0 and < 90) |
||||
|
{ |
||||
|
PredictZone1Scalar(destination, destinationStride, above, upsampleAbove, GetDeltaX(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 90 and < 180) |
||||
|
{ |
||||
|
PredictZone2Scalar(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, GetDeltaX(angle), GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else if (angle is > 180 and < 270) |
||||
|
{ |
||||
|
PredictZone3Scalar(destination, destinationStride, left, upsampleLeft, GetDeltaY(angle), width, height); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Av1PredictionMode mode = angle == 90 ? Av1PredictionMode.Vertical : Av1PredictionMode.Horizontal; |
||||
|
Av1IntraPredictorBase.GetPredictor(mode).PredictScalar(destination, destinationStride, above, left, width, height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the horizontal Q8 projection derivative for an adjusted angle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
/// <returns>The horizontal derivative, or one when the selected zone does not consume it.</returns>
|
||||
|
public static int GetDeltaX(int angle) |
||||
|
=> angle switch |
||||
|
{ |
||||
|
> 0 and < 90 => DirectionalIntraDerivative[angle], |
||||
|
> 90 and < 180 => DirectionalIntraDerivative[180 - angle], |
||||
|
_ => 1, |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the vertical Q8 projection derivative for an adjusted angle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="angle">The adjusted prediction angle.</param>
|
||||
|
/// <returns>The vertical derivative, or one when the selected zone does not consume it.</returns>
|
||||
|
public static int GetDeltaY(int angle) |
||||
|
=> angle switch |
||||
|
{ |
||||
|
> 90 and < 180 => DirectionalIntraDerivative[angle - 90], |
||||
|
> 180 and < 270 => DirectionalIntraDerivative[270 - angle], |
||||
|
_ => 1, |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit zone 1 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone1Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int basisIncrement = 1 << upsampleShift; |
||||
|
int projection = derivative; |
||||
|
ref byte aboveBase = ref Unsafe.AsRef(in above[0]); |
||||
|
|
||||
|
for (int row = 0; row < height; row++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
ref byte destinationRow = ref destination[row * destinationStride]; |
||||
|
|
||||
|
for (int column = 0; column < width; column++, basis += basisIncrement) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis |
||||
|
? (byte)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5) |
||||
|
: Unsafe.Add(ref aboveBase, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth zone 1 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone1Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int basisIncrement = 1 << upsampleShift; |
||||
|
int projection = derivative; |
||||
|
ref short aboveBase = ref Unsafe.AsRef(in above[0]); |
||||
|
|
||||
|
for (int row = 0; row < height; row++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
ref short destinationRow = ref destination[row * destinationStride]; |
||||
|
|
||||
|
for (int column = 0; column < width; column++, basis += basisIncrement) |
||||
|
{ |
||||
|
Unsafe.Add(ref destinationRow, column) = basis < maximumBasis |
||||
|
? (short)(((Unsafe.Add(ref aboveBase, basis) * (32 - weight)) + (Unsafe.Add(ref aboveBase, basis + 1) * weight) + 16) >> 5) |
||||
|
: Unsafe.Add(ref aboveBase, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit zone 2 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
||||
|
/// <param name="dx">The horizontal Q8 derivative.</param>
|
||||
|
/// <param name="dy">The vertical Q8 derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone2Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
||||
|
{ |
||||
|
int aboveShift = upsampleAbove ? 1 : 0; |
||||
|
int leftShift = upsampleLeft ? 1 : 0; |
||||
|
int minimumTopBasis = -(1 << aboveShift); |
||||
|
int topFractionBits = 6 - aboveShift; |
||||
|
int leftFractionBits = 6 - leftShift; |
||||
|
int topBasisIncrement = 1 << aboveShift; |
||||
|
int topProjection = -dx; |
||||
|
ref byte aboveBase = ref Unsafe.AsRef(in above[0]); |
||||
|
ref byte leftBase = ref Unsafe.AsRef(in left[0]); |
||||
|
|
||||
|
for (int row = 0; row < height; row++, topProjection -= dx) |
||||
|
{ |
||||
|
int topBasis = topProjection >> topFractionBits; |
||||
|
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
||||
|
int leftProjection = (row << 6) - dy; |
||||
|
ref byte destinationRow = ref destination[row * destinationStride]; |
||||
|
|
||||
|
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy) |
||||
|
{ |
||||
|
int prediction; |
||||
|
if (topBasis >= minimumTopBasis) |
||||
|
{ |
||||
|
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int leftBasis = leftProjection >> leftFractionBits; |
||||
|
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1; |
||||
|
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight); |
||||
|
} |
||||
|
|
||||
|
Unsafe.Add(ref destinationRow, column) = (byte)((prediction + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth zone 2 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="above">The projected top reference.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsampleAbove">Whether the top reference contains half-sample positions.</param>
|
||||
|
/// <param name="upsampleLeft">Whether the left reference contains half-sample positions.</param>
|
||||
|
/// <param name="dx">The horizontal Q8 derivative.</param>
|
||||
|
/// <param name="dy">The vertical Q8 derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone2Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy, int width, int height) |
||||
|
{ |
||||
|
int aboveShift = upsampleAbove ? 1 : 0; |
||||
|
int leftShift = upsampleLeft ? 1 : 0; |
||||
|
int minimumTopBasis = -(1 << aboveShift); |
||||
|
int topFractionBits = 6 - aboveShift; |
||||
|
int leftFractionBits = 6 - leftShift; |
||||
|
int topBasisIncrement = 1 << aboveShift; |
||||
|
int topProjection = -dx; |
||||
|
ref short aboveBase = ref Unsafe.AsRef(in above[0]); |
||||
|
ref short leftBase = ref Unsafe.AsRef(in left[0]); |
||||
|
|
||||
|
for (int row = 0; row < height; row++, topProjection -= dx) |
||||
|
{ |
||||
|
int topBasis = topProjection >> topFractionBits; |
||||
|
int topWeight = ((topProjection << aboveShift) & 0x3F) >> 1; |
||||
|
int leftProjection = (row << 6) - dy; |
||||
|
ref short destinationRow = ref destination[row * destinationStride]; |
||||
|
|
||||
|
for (int column = 0; column < width; column++, topBasis += topBasisIncrement, leftProjection -= dy) |
||||
|
{ |
||||
|
int prediction; |
||||
|
if (topBasis >= minimumTopBasis) |
||||
|
{ |
||||
|
prediction = (Unsafe.Add(ref aboveBase, topBasis) * (32 - topWeight)) + (Unsafe.Add(ref aboveBase, topBasis + 1) * topWeight); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
int leftBasis = leftProjection >> leftFractionBits; |
||||
|
int leftWeight = ((leftProjection << leftShift) & 0x3F) >> 1; |
||||
|
prediction = (Unsafe.Add(ref leftBase, leftBasis) * (32 - leftWeight)) + (Unsafe.Add(ref leftBase, leftBasis + 1) * leftWeight); |
||||
|
} |
||||
|
|
||||
|
Unsafe.Add(ref destinationRow, column) = (short)((prediction + 16) >> 5); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit zone 3 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone3Scalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> left, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int basisIncrement = 1 << upsampleShift; |
||||
|
int projection = derivative; |
||||
|
ref byte leftBase = ref Unsafe.AsRef(in left[0]); |
||||
|
|
||||
|
for (int column = 0; column < width; column++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
for (int row = 0; row < height; row++, basis += basisIncrement) |
||||
|
{ |
||||
|
destination[(row * destinationStride) + column] = basis < maximumBasis |
||||
|
? (byte)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5) |
||||
|
: Unsafe.Add(ref leftBase, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth zone 3 block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="left">The projected left reference.</param>
|
||||
|
/// <param name="upsample">Whether the reference contains half-sample positions.</param>
|
||||
|
/// <param name="derivative">The Q8 projection derivative.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void PredictZone3Scalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> left, bool upsample, int derivative, int width, int height) |
||||
|
{ |
||||
|
int upsampleShift = upsample ? 1 : 0; |
||||
|
int maximumBasis = (width + height - 1) << upsampleShift; |
||||
|
int fractionBits = 6 - upsampleShift; |
||||
|
int basisIncrement = 1 << upsampleShift; |
||||
|
int projection = derivative; |
||||
|
ref short leftBase = ref Unsafe.AsRef(in left[0]); |
||||
|
|
||||
|
for (int column = 0; column < width; column++, projection += derivative) |
||||
|
{ |
||||
|
int basis = projection >> fractionBits; |
||||
|
int weight = ((projection << upsampleShift) & 0x3F) >> 1; |
||||
|
for (int row = 0; row < height; row++, basis += basisIncrement) |
||||
|
{ |
||||
|
destination[(row * destinationStride) + column] = basis < maximumBasis |
||||
|
? (short)(((Unsafe.Add(ref leftBase, basis) * (32 - weight)) + (Unsafe.Add(ref leftBase, basis + 1) * weight) + 16) >> 5) |
||||
|
: Unsafe.Add(ref leftBase, maximumBasis); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,118 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces 8-bit AV1 directional intra predictions for angles in zone 1.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Zone 1 projects the top reference samples into the block for prediction angles less than 90 degrees.
|
|
||||
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
|
|
||||
/// </remarks>
|
|
||||
internal readonly struct Av1DirectionalZone1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The width of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The height of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone1Predictor"/> struct for the specified block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The dimensions of the prediction block.</param>
|
|
||||
public Av1DirectionalZone1Predictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone1Predictor"/> struct for the specified transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
public Av1DirectionalZone1Predictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 1 directional prediction for a transform block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, bool upsampleAbove, int dx) |
|
||||
=> new Av1DirectionalZone1Predictor(transformSize).PredictScalar(destination, stride, above, upsampleAbove, dx); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 1 directional prediction for this block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsample">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z1_c</c> in SVT-AV1.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, bool upsample, int dx) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
int upsampleAbove = upsample ? 1 : 0; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
int maxBasisX = (((int)this.blockWidth + (int)this.blockHeight) - 1) << upsampleAbove; |
|
||||
int fractionBitCount = 6 - upsampleAbove; |
|
||||
int basisIncrement = 1 << upsampleAbove; |
|
||||
int x = dx; |
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
// AV1 retains six fractional projection bits, or five after reference upsampling.
|
|
||||
// The interpolation weights sum to 32 because the low projection bit is discarded.
|
|
||||
int basis = x >> fractionBitCount, shift = ((x << upsampleAbove) & 0x3F) >> 1; |
|
||||
|
|
||||
if (basis >= maxBasisX) |
|
||||
{ |
|
||||
for (nuint i = r; i < this.blockHeight; ++i) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref aboveRef, maxBasisX), (uint)this.blockWidth); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
for (nuint c = 0; c < this.blockWidth; ++c) |
|
||||
{ |
|
||||
if (basis < maxBasisX) |
|
||||
{ |
|
||||
int val; |
|
||||
val = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift); |
|
||||
val = Av1Math.RoundPowerOf2(val, 5); |
|
||||
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Unsafe.Add(ref destinationRef, c) = Unsafe.Add(ref aboveRef, maxBasisX); |
|
||||
} |
|
||||
|
|
||||
basis += basisIncrement; |
|
||||
} |
|
||||
|
|
||||
x += dx; |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,124 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Security.Cryptography; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces 8-bit AV1 directional intra predictions for angles in zone 2.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Zone 2 projects both top and left reference samples into the block for prediction angles between 90 and 180 degrees.
|
|
||||
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
|
|
||||
/// </remarks>
|
|
||||
internal readonly struct Av1DirectionalZone2Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The width of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The height of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone2Predictor"/> struct for the specified block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The dimensions of the prediction block.</param>
|
|
||||
public Av1DirectionalZone2Predictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone2Predictor"/> struct for the specified transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
public Av1DirectionalZone2Predictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 2 directional prediction for a transform block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool upsampleAbove, bool upsampleLeft, int dx, int dy) |
|
||||
=> new Av1DirectionalZone2Predictor(transformSize).PredictScalar(destination, stride, above, left, upsampleAbove, upsampleLeft, dx, dy); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 2 directional prediction for this block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="doUpsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="doUpsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z2_c</c> in SVT-AV1.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
int upsampleAbove = doUpsampleAbove ? 1 : 0; |
|
||||
int upsampleLeft = doUpsampleLeft ? 1 : 0; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
int minBasisX = -(1 << upsampleAbove); |
|
||||
int fractionBitCountX = 6 - upsampleAbove; |
|
||||
int fractionBitCountY = 6 - upsampleLeft; |
|
||||
int basisIncrementX = 1 << upsampleAbove; |
|
||||
int x = -dx; |
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
int val; |
|
||||
int base1 = x >> fractionBitCountX; |
|
||||
int y = ((int)r << 6) - dy; |
|
||||
for (nuint c = 0; c < this.blockWidth; ++c, base1 += basisIncrementX, y -= dy) |
|
||||
{ |
|
||||
// A nonnegative top projection uses the above edge. Once the projection crosses
|
|
||||
// the top-left corner, the same destination sample is projected from the left edge.
|
|
||||
if (base1 >= minBasisX) |
|
||||
{ |
|
||||
int shift1 = ((x * (1 << upsampleAbove)) & 0x3F) >> 1; |
|
||||
val = (Unsafe.Add(ref aboveRef, base1) * (32 - shift1)) + (Unsafe.Add(ref aboveRef, base1 + 1) * shift1); |
|
||||
val = Av1Math.RoundPowerOf2(val, 5); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
int base2 = y >> fractionBitCountY; |
|
||||
Guard.MustBeGreaterThanOrEqualTo(base2, -(1 << upsampleLeft), nameof(base2)); |
|
||||
int shift2 = ((y * (1 << upsampleLeft)) & 0x3F) >> 1; |
|
||||
val = (Unsafe.Add(ref leftRef, base2) * (32 - shift2)) + (Unsafe.Add(ref leftRef, base2 + 1) * shift2); |
|
||||
val = Av1Math.RoundPowerOf2(val, 5); |
|
||||
} |
|
||||
|
|
||||
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.Clamp(val, 0, 255); |
|
||||
} |
|
||||
|
|
||||
x -= dx; |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,119 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces 8-bit AV1 directional intra predictions for angles in zone 3.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Zone 3 projects the left reference samples into the block for prediction angles greater than 180 degrees.
|
|
||||
/// The scalar prediction follows the directional prediction process in section 7.11.2.4 of the AV1 specification.
|
|
||||
/// </remarks>
|
|
||||
internal readonly struct Av1DirectionalZone3Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The width of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The height of the prediction block in samples.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone3Predictor"/> struct for the specified block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The dimensions of the prediction block.</param>
|
|
||||
public Av1DirectionalZone3Predictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1DirectionalZone3Predictor"/> struct for the specified transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
public Av1DirectionalZone3Predictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 3 directional prediction for a transform block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> left, bool upsampleLeft, int dx, int dy) |
|
||||
=> new Av1DirectionalZone3Predictor(transformSize).PredictScalar(destination, stride, left, upsampleLeft, dx, dy); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit zone 3 directional prediction for this block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsample">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
/// <remarks>Corresponds to <c>svt_av1_dr_prediction_z3_c</c> in SVT-AV1.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> left, bool upsample, int dx, int dy) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
int upsampleLeft = upsample ? 1 : 0; |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction."); |
|
||||
Guard.MustBeGreaterThan(dy, 0, nameof(dy)); |
|
||||
|
|
||||
int maxBasisY = ((int)this.blockWidth + (int)this.blockHeight - 1) << upsampleLeft; |
|
||||
int fractionBitCount = 6 - upsampleLeft; |
|
||||
int basisIncrement = 1 << upsampleLeft; |
|
||||
int y = dy; |
|
||||
for (nuint c = 0; c < this.blockWidth; ++c) |
|
||||
{ |
|
||||
// Zone 3 is the transpose of zone 1: columns advance along the projected left edge,
|
|
||||
// while rows advance through the reference samples for each destination column.
|
|
||||
int basis = y >> fractionBitCount; |
|
||||
int shift = ((y << upsampleLeft) & 0x3F) >> 1; |
|
||||
|
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
if (basis < maxBasisY) |
|
||||
{ |
|
||||
int val; |
|
||||
val = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift); |
|
||||
val = Av1Math.RoundPowerOf2(val, 5); |
|
||||
Unsafe.Add(ref destinationRef, (r * stride) + c) = (byte)Av1Math.Clamp(val, 0, 255); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
for (; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
Unsafe.Add(ref destinationRef, (r * stride) + c) = left[maxBasisY]; |
|
||||
} |
|
||||
|
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
basis += basisIncrement; |
|
||||
} |
|
||||
|
|
||||
y += dy; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the AV1 DC filter-intra coefficient operator.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct DcOperator : IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.DC; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static ReadOnlySpan<sbyte> Taps => |
||||
|
[ |
||||
|
-6, 10, 0, 0, 0, 12, 0, |
||||
|
-5, 2, 10, 0, 0, 9, 0, |
||||
|
-3, 1, 1, 10, 0, 7, 0, |
||||
|
-3, 1, 1, 2, 10, 5, 0, |
||||
|
-4, 6, 0, 0, 0, 2, 12, |
||||
|
-3, 2, 6, 0, 0, 2, 9, |
||||
|
-3, 2, 2, 6, 0, 2, 7, |
||||
|
-3, 1, 2, 2, 6, 3, 5, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the AV1 157-degree directional filter-intra coefficient operator.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct Directional157Operator : IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Directional157; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static ReadOnlySpan<sbyte> Taps => |
||||
|
[ |
||||
|
-2, 8, 0, 0, 0, 10, 0, |
||||
|
-1, 3, 8, 0, 0, 6, 0, |
||||
|
-1, 2, 3, 8, 0, 4, 0, |
||||
|
0, 1, 2, 3, 8, 2, 0, |
||||
|
-1, 4, 0, 0, 0, 3, 10, |
||||
|
-1, 3, 4, 0, 0, 4, 6, |
||||
|
-1, 2, 3, 4, 0, 4, 4, |
||||
|
-1, 2, 2, 3, 4, 3, 3, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the AV1 horizontal filter-intra coefficient operator.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct HorizontalOperator : IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Horizontal; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static ReadOnlySpan<sbyte> Taps => |
||||
|
[ |
||||
|
-8, 8, 0, 0, 0, 16, 0, |
||||
|
-8, 0, 8, 0, 0, 16, 0, |
||||
|
-8, 0, 0, 8, 0, 16, 0, |
||||
|
-8, 0, 0, 0, 8, 16, 0, |
||||
|
-4, 4, 0, 0, 0, 0, 16, |
||||
|
-4, 0, 4, 0, 0, 0, 16, |
||||
|
-4, 0, 0, 4, 0, 0, 16, |
||||
|
-4, 0, 0, 0, 4, 0, 16, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,596 @@ |
|||||
|
// 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.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Applies one closed filter-intra coefficient operator using the widest useful SIMD width.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The filter-intra coefficient set.</typeparam>
|
||||
|
internal sealed class Av1FilterIntraPredictor<TOperator> : Av1FilterIntraPredictorBase |
||||
|
where TOperator : struct, IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override Av1FilterIntraMode Mode => TOperator.Mode; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch) |
||||
|
{ |
||||
|
Span<byte> buffer = scratch[..ScratchLength]; |
||||
|
ref byte bufferBase = ref MemoryMarshal.GetReference(buffer); |
||||
|
ref byte aboveBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref byte leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps); |
||||
|
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1)); |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<int> tap0 = CreateTapVector256(ref taps, 0); |
||||
|
Vector256<int> tap1 = CreateTapVector256(ref taps, 1); |
||||
|
Vector256<int> tap2 = CreateTapVector256(ref taps, 2); |
||||
|
Vector256<int> tap3 = CreateTapVector256(ref taps, 3); |
||||
|
Vector256<int> tap4 = CreateTapVector256(ref taps, 4); |
||||
|
Vector256<int> tap5 = CreateTapVector256(ref taps, 5); |
||||
|
Vector256<int> tap6 = CreateTapVector256(ref taps, 6); |
||||
|
Vector256<int> rounding = Vector256.Create(8); |
||||
|
Vector256<int> maximum = Vector256.Create(255); |
||||
|
|
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
Vector256<int> prediction = Calculate( |
||||
|
tap0, |
||||
|
tap1, |
||||
|
tap2, |
||||
|
tap3, |
||||
|
tap4, |
||||
|
tap5, |
||||
|
tap6, |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 1), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 2), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 3), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 4), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + BufferStride), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride))); |
||||
|
|
||||
|
prediction = Vector256.Clamp((prediction + rounding) >> 4, Vector256<int>.Zero, maximum); |
||||
|
StoreEightBytes(prediction, ref bufferBase, (row * BufferStride) + column, ((row + 1) * BufferStride) + column); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<int> tap0Low = CreateTapVector128(ref taps, 0, 0); |
||||
|
Vector128<int> tap1Low = CreateTapVector128(ref taps, 1, 0); |
||||
|
Vector128<int> tap2Low = CreateTapVector128(ref taps, 2, 0); |
||||
|
Vector128<int> tap3Low = CreateTapVector128(ref taps, 3, 0); |
||||
|
Vector128<int> tap4Low = CreateTapVector128(ref taps, 4, 0); |
||||
|
Vector128<int> tap5Low = CreateTapVector128(ref taps, 5, 0); |
||||
|
Vector128<int> tap6Low = CreateTapVector128(ref taps, 6, 0); |
||||
|
Vector128<int> tap0High = CreateTapVector128(ref taps, 0, 4); |
||||
|
Vector128<int> tap1High = CreateTapVector128(ref taps, 1, 4); |
||||
|
Vector128<int> tap2High = CreateTapVector128(ref taps, 2, 4); |
||||
|
Vector128<int> tap3High = CreateTapVector128(ref taps, 3, 4); |
||||
|
Vector128<int> tap4High = CreateTapVector128(ref taps, 4, 4); |
||||
|
Vector128<int> tap5High = CreateTapVector128(ref taps, 5, 4); |
||||
|
Vector128<int> tap6High = CreateTapVector128(ref taps, 6, 4); |
||||
|
Vector128<int> rounding = Vector128.Create(8); |
||||
|
Vector128<int> maximum = Vector128.Create(255); |
||||
|
|
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
int p0 = Unsafe.Add(ref bufferBase, sourceOffset); |
||||
|
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1); |
||||
|
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2); |
||||
|
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3); |
||||
|
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4); |
||||
|
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride); |
||||
|
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)); |
||||
|
Vector128<int> low = Calculate(tap0Low, tap1Low, tap2Low, tap3Low, tap4Low, tap5Low, tap6Low, p0, p1, p2, p3, p4, p5, p6); |
||||
|
Vector128<int> high = Calculate(tap0High, tap1High, tap2High, tap3High, tap4High, tap5High, tap6High, p0, p1, p2, p3, p4, p5, p6); |
||||
|
low = Vector128.Clamp((low + rounding) >> 4, Vector128<int>.Zero, maximum); |
||||
|
high = Vector128.Clamp((high + rounding) >> 4, Vector128<int>.Zero, maximum); |
||||
|
StoreFourBytes(low, ref bufferBase, (row * BufferStride) + column); |
||||
|
StoreFourBytes(high, ref bufferBase, ((row + 1) * BufferStride) + column); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PredictGroupsScalar(buffer, width, height, 255, ref taps); |
||||
|
} |
||||
|
|
||||
|
CopyToDestination(buffer, destination, destinationStride, width, height); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch) |
||||
|
{ |
||||
|
Span<short> buffer = scratch[..ScratchLength]; |
||||
|
ref short bufferBase = ref MemoryMarshal.GetReference(buffer); |
||||
|
ref short aboveBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps); |
||||
|
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1)); |
||||
|
int maximum = (1 << bitDepth) - 1; |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector256<int> tap0 = CreateTapVector256(ref taps, 0); |
||||
|
Vector256<int> tap1 = CreateTapVector256(ref taps, 1); |
||||
|
Vector256<int> tap2 = CreateTapVector256(ref taps, 2); |
||||
|
Vector256<int> tap3 = CreateTapVector256(ref taps, 3); |
||||
|
Vector256<int> tap4 = CreateTapVector256(ref taps, 4); |
||||
|
Vector256<int> tap5 = CreateTapVector256(ref taps, 5); |
||||
|
Vector256<int> tap6 = CreateTapVector256(ref taps, 6); |
||||
|
Vector256<int> rounding = Vector256.Create(8); |
||||
|
Vector256<int> maximumVector = Vector256.Create(maximum); |
||||
|
|
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
Vector256<int> prediction = Calculate( |
||||
|
tap0, |
||||
|
tap1, |
||||
|
tap2, |
||||
|
tap3, |
||||
|
tap4, |
||||
|
tap5, |
||||
|
tap6, |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 1), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 2), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 3), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + 4), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + BufferStride), |
||||
|
Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride))); |
||||
|
|
||||
|
prediction = Vector256.Clamp((prediction + rounding) >> 4, Vector256<int>.Zero, maximumVector); |
||||
|
StoreEightShorts(prediction, ref bufferBase, (row * BufferStride) + column, ((row + 1) * BufferStride) + column); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
Vector128<int> tap0Low = CreateTapVector128(ref taps, 0, 0); |
||||
|
Vector128<int> tap1Low = CreateTapVector128(ref taps, 1, 0); |
||||
|
Vector128<int> tap2Low = CreateTapVector128(ref taps, 2, 0); |
||||
|
Vector128<int> tap3Low = CreateTapVector128(ref taps, 3, 0); |
||||
|
Vector128<int> tap4Low = CreateTapVector128(ref taps, 4, 0); |
||||
|
Vector128<int> tap5Low = CreateTapVector128(ref taps, 5, 0); |
||||
|
Vector128<int> tap6Low = CreateTapVector128(ref taps, 6, 0); |
||||
|
Vector128<int> tap0High = CreateTapVector128(ref taps, 0, 4); |
||||
|
Vector128<int> tap1High = CreateTapVector128(ref taps, 1, 4); |
||||
|
Vector128<int> tap2High = CreateTapVector128(ref taps, 2, 4); |
||||
|
Vector128<int> tap3High = CreateTapVector128(ref taps, 3, 4); |
||||
|
Vector128<int> tap4High = CreateTapVector128(ref taps, 4, 4); |
||||
|
Vector128<int> tap5High = CreateTapVector128(ref taps, 5, 4); |
||||
|
Vector128<int> tap6High = CreateTapVector128(ref taps, 6, 4); |
||||
|
Vector128<int> rounding = Vector128.Create(8); |
||||
|
Vector128<int> maximumVector = Vector128.Create(maximum); |
||||
|
|
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
int p0 = Unsafe.Add(ref bufferBase, sourceOffset); |
||||
|
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1); |
||||
|
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2); |
||||
|
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3); |
||||
|
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4); |
||||
|
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride); |
||||
|
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)); |
||||
|
Vector128<int> low = Calculate(tap0Low, tap1Low, tap2Low, tap3Low, tap4Low, tap5Low, tap6Low, p0, p1, p2, p3, p4, p5, p6); |
||||
|
Vector128<int> high = Calculate(tap0High, tap1High, tap2High, tap3High, tap4High, tap5High, tap6High, p0, p1, p2, p3, p4, p5, p6); |
||||
|
low = Vector128.Clamp((low + rounding) >> 4, Vector128<int>.Zero, maximumVector); |
||||
|
high = Vector128.Clamp((high + rounding) >> 4, Vector128<int>.Zero, maximumVector); |
||||
|
StoreFourShorts(low, ref bufferBase, (row * BufferStride) + column); |
||||
|
StoreFourShorts(high, ref bufferBase, ((row + 1) * BufferStride) + column); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PredictGroupsScalar(buffer, width, height, maximum, ref taps); |
||||
|
} |
||||
|
|
||||
|
CopyToDestination(buffer, destination, destinationStride, width, height); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch) |
||||
|
{ |
||||
|
Span<byte> buffer = scratch[..ScratchLength]; |
||||
|
ref byte aboveBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps); |
||||
|
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1)); |
||||
|
PredictGroupsScalar(buffer, width, height, 255, ref taps); |
||||
|
CopyToDestinationScalar(buffer, destination, destinationStride, width, height); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch) |
||||
|
{ |
||||
|
Span<short> buffer = scratch[..ScratchLength]; |
||||
|
ref short aboveBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref sbyte taps = ref MemoryMarshal.GetReference(TOperator.Taps); |
||||
|
Initialize(buffer, above, left, width, height, Unsafe.Subtract(ref aboveBase, 1)); |
||||
|
PredictGroupsScalar(buffer, width, height, (1 << bitDepth) - 1, ref taps); |
||||
|
CopyToDestinationScalar(buffer, destination, destinationStride, width, height); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes an 8-bit recursive filter-intra workspace from the prepared top and left references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The recursive workspace.</param>
|
||||
|
/// <param name="above">The top reference.</param>
|
||||
|
/// <param name="left">The left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference.</param>
|
||||
|
private static void Initialize(Span<byte> buffer, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, byte topLeft) |
||||
|
{ |
||||
|
buffer[0] = topLeft; |
||||
|
above[..width].CopyTo(buffer[1..]); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
buffer[(row + 1) * BufferStride] = left[row]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a high-bit-depth recursive filter-intra workspace from the prepared top and left references.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The recursive workspace.</param>
|
||||
|
/// <param name="above">The top reference.</param>
|
||||
|
/// <param name="left">The left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference.</param>
|
||||
|
private static void Initialize(Span<short> buffer, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, short topLeft) |
||||
|
{ |
||||
|
buffer[0] = topLeft; |
||||
|
above[..width].CopyTo(buffer[1..]); |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
buffer[(row + 1) * BufferStride] = left[row]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the coefficients for eight output samples at one tap position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="taps">The first coefficient in the operator table.</param>
|
||||
|
/// <param name="tap">The tap position.</param>
|
||||
|
/// <returns>The coefficient vector.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> CreateTapVector256(ref sbyte taps, int tap) |
||||
|
=> Vector256.Create( |
||||
|
(int)Unsafe.Add(ref taps, tap), |
||||
|
Unsafe.Add(ref taps, 7 + tap), |
||||
|
Unsafe.Add(ref taps, 14 + tap), |
||||
|
Unsafe.Add(ref taps, 21 + tap), |
||||
|
Unsafe.Add(ref taps, 28 + tap), |
||||
|
Unsafe.Add(ref taps, 35 + tap), |
||||
|
Unsafe.Add(ref taps, 42 + tap), |
||||
|
Unsafe.Add(ref taps, 49 + tap)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates the coefficients for four output samples at one tap position.
|
||||
|
/// </summary>
|
||||
|
/// <param name="taps">The first coefficient in the operator table.</param>
|
||||
|
/// <param name="tap">The tap position.</param>
|
||||
|
/// <param name="pixel">The first output sample.</param>
|
||||
|
/// <returns>The coefficient vector.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> CreateTapVector128(ref sbyte taps, int tap, int pixel) |
||||
|
=> Vector128.Create( |
||||
|
(int)Unsafe.Add(ref taps, (pixel * 7) + tap), |
||||
|
Unsafe.Add(ref taps, ((pixel + 1) * 7) + tap), |
||||
|
Unsafe.Add(ref taps, ((pixel + 2) * 7) + tap), |
||||
|
Unsafe.Add(ref taps, ((pixel + 3) * 7) + tap)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates eight filtered predictions from seven reconstructed samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tap0">The first tap coefficients.</param>
|
||||
|
/// <param name="tap1">The second tap coefficients.</param>
|
||||
|
/// <param name="tap2">The third tap coefficients.</param>
|
||||
|
/// <param name="tap3">The fourth tap coefficients.</param>
|
||||
|
/// <param name="tap4">The fifth tap coefficients.</param>
|
||||
|
/// <param name="tap5">The sixth tap coefficients.</param>
|
||||
|
/// <param name="tap6">The seventh tap coefficients.</param>
|
||||
|
/// <param name="p0">The first reconstructed sample.</param>
|
||||
|
/// <param name="p1">The second reconstructed sample.</param>
|
||||
|
/// <param name="p2">The third reconstructed sample.</param>
|
||||
|
/// <param name="p3">The fourth reconstructed sample.</param>
|
||||
|
/// <param name="p4">The fifth reconstructed sample.</param>
|
||||
|
/// <param name="p5">The sixth reconstructed sample.</param>
|
||||
|
/// <param name="p6">The seventh reconstructed sample.</param>
|
||||
|
/// <returns>The unrounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> Calculate( |
||||
|
Vector256<int> tap0, |
||||
|
Vector256<int> tap1, |
||||
|
Vector256<int> tap2, |
||||
|
Vector256<int> tap3, |
||||
|
Vector256<int> tap4, |
||||
|
Vector256<int> tap5, |
||||
|
Vector256<int> tap6, |
||||
|
int p0, |
||||
|
int p1, |
||||
|
int p2, |
||||
|
int p3, |
||||
|
int p4, |
||||
|
int p5, |
||||
|
int p6) |
||||
|
=> (tap0 * p0) + (tap1 * p1) + (tap2 * p2) + (tap3 * p3) + (tap4 * p4) + (tap5 * p5) + (tap6 * p6); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates four filtered predictions from seven reconstructed samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tap0">The first tap coefficients.</param>
|
||||
|
/// <param name="tap1">The second tap coefficients.</param>
|
||||
|
/// <param name="tap2">The third tap coefficients.</param>
|
||||
|
/// <param name="tap3">The fourth tap coefficients.</param>
|
||||
|
/// <param name="tap4">The fifth tap coefficients.</param>
|
||||
|
/// <param name="tap5">The sixth tap coefficients.</param>
|
||||
|
/// <param name="tap6">The seventh tap coefficients.</param>
|
||||
|
/// <param name="p0">The first reconstructed sample.</param>
|
||||
|
/// <param name="p1">The second reconstructed sample.</param>
|
||||
|
/// <param name="p2">The third reconstructed sample.</param>
|
||||
|
/// <param name="p3">The fourth reconstructed sample.</param>
|
||||
|
/// <param name="p4">The fifth reconstructed sample.</param>
|
||||
|
/// <param name="p5">The sixth reconstructed sample.</param>
|
||||
|
/// <param name="p6">The seventh reconstructed sample.</param>
|
||||
|
/// <returns>The unrounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Calculate( |
||||
|
Vector128<int> tap0, |
||||
|
Vector128<int> tap1, |
||||
|
Vector128<int> tap2, |
||||
|
Vector128<int> tap3, |
||||
|
Vector128<int> tap4, |
||||
|
Vector128<int> tap5, |
||||
|
Vector128<int> tap6, |
||||
|
int p0, |
||||
|
int p1, |
||||
|
int p2, |
||||
|
int p3, |
||||
|
int p4, |
||||
|
int p5, |
||||
|
int p6) |
||||
|
=> (tap0 * p0) + (tap1 * p1) + (tap2 * p2) + (tap3 * p3) + (tap4 * p4) + (tap5 * p5) + (tap6 * p6); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores eight 8-bit predictions into the two recursive output rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The clamped predictions.</param>
|
||||
|
/// <param name="buffer">The recursive workspace origin.</param>
|
||||
|
/// <param name="firstRowOffset">The first output-row offset.</param>
|
||||
|
/// <param name="secondRowOffset">The second output-row offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreEightBytes(Vector256<int> prediction, ref byte buffer, int firstRowOffset, int secondRowOffset) |
||||
|
{ |
||||
|
Vector256<ushort> narrowed16 = Vector256.Narrow(prediction.AsUInt32(), Vector256<uint>.Zero); |
||||
|
Vector256<byte> narrowed8 = Vector256.Narrow(narrowed16, Vector256<ushort>.Zero); |
||||
|
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, firstRowOffset)) = narrowed8.AsUInt32().GetElement(0); |
||||
|
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, secondRowOffset)) = narrowed8.AsUInt32().GetElement(1); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four 8-bit predictions into one recursive output row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The clamped predictions.</param>
|
||||
|
/// <param name="buffer">The recursive workspace origin.</param>
|
||||
|
/// <param name="offset">The output-row offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourBytes(Vector128<int> prediction, ref byte buffer, int offset) |
||||
|
{ |
||||
|
Vector128<ushort> narrowed16 = Vector128.Narrow(prediction.AsUInt32(), Vector128<uint>.Zero); |
||||
|
Vector128<byte> narrowed8 = Vector128.Narrow(narrowed16, Vector128<ushort>.Zero); |
||||
|
Unsafe.As<byte, uint>(ref Unsafe.Add(ref buffer, offset)) = narrowed8.AsUInt32().GetElement(0); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores eight high-bit-depth predictions into the two recursive output rows.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The clamped predictions.</param>
|
||||
|
/// <param name="buffer">The recursive workspace origin.</param>
|
||||
|
/// <param name="firstRowOffset">The first output-row offset.</param>
|
||||
|
/// <param name="secondRowOffset">The second output-row offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreEightShorts(Vector256<int> prediction, ref short buffer, int firstRowOffset, int secondRowOffset) |
||||
|
{ |
||||
|
Vector256<short> narrowed = Vector256.Narrow(prediction, Vector256<int>.Zero); |
||||
|
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, firstRowOffset)) = narrowed.AsUInt64().GetElement(0); |
||||
|
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, secondRowOffset)) = narrowed.AsUInt64().GetElement(1); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Stores four high-bit-depth predictions into one recursive output row.
|
||||
|
/// </summary>
|
||||
|
/// <param name="prediction">The clamped predictions.</param>
|
||||
|
/// <param name="buffer">The recursive workspace origin.</param>
|
||||
|
/// <param name="offset">The output-row offset.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void StoreFourShorts(Vector128<int> prediction, ref short buffer, int offset) |
||||
|
{ |
||||
|
Vector128<short> narrowed = Vector128.Narrow(prediction, Vector128<int>.Zero); |
||||
|
Unsafe.As<short, ulong>(ref Unsafe.Add(ref buffer, offset)) = narrowed.AsUInt64().GetElement(0); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates every 8-bit recursive filter group without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The initialized recursive workspace.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="maximum">The maximum reconstructed sample.</param>
|
||||
|
/// <param name="taps">The first filter coefficient.</param>
|
||||
|
private static void PredictGroupsScalar(Span<byte> buffer, int width, int height, int maximum, ref sbyte taps) |
||||
|
{ |
||||
|
ref byte bufferBase = ref MemoryMarshal.GetReference(buffer); |
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
int p0 = Unsafe.Add(ref bufferBase, sourceOffset); |
||||
|
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1); |
||||
|
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2); |
||||
|
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3); |
||||
|
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4); |
||||
|
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride); |
||||
|
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)); |
||||
|
for (int pixel = 0; pixel < 8; pixel++) |
||||
|
{ |
||||
|
int tapOffset = pixel * 7; |
||||
|
int prediction = |
||||
|
(Unsafe.Add(ref taps, tapOffset) * p0) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 1) * p1) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 2) * p2) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 3) * p3) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 4) * p4) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 5) * p5) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 6) * p6); |
||||
|
|
||||
|
int destinationOffset = ((row + (pixel >> 2)) * BufferStride) + column + (pixel & 3); |
||||
|
Unsafe.Add(ref bufferBase, destinationOffset) = (byte)Math.Clamp((prediction + 8) >> 4, 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates every high-bit-depth recursive filter group without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The initialized recursive workspace.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="maximum">The maximum reconstructed sample.</param>
|
||||
|
/// <param name="taps">The first filter coefficient.</param>
|
||||
|
private static void PredictGroupsScalar(Span<short> buffer, int width, int height, int maximum, ref sbyte taps) |
||||
|
{ |
||||
|
ref short bufferBase = ref MemoryMarshal.GetReference(buffer); |
||||
|
for (int row = 1; row <= height; row += 2) |
||||
|
{ |
||||
|
for (int column = 1; column <= width; column += 4) |
||||
|
{ |
||||
|
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
||||
|
int p0 = Unsafe.Add(ref bufferBase, sourceOffset); |
||||
|
int p1 = Unsafe.Add(ref bufferBase, sourceOffset + 1); |
||||
|
int p2 = Unsafe.Add(ref bufferBase, sourceOffset + 2); |
||||
|
int p3 = Unsafe.Add(ref bufferBase, sourceOffset + 3); |
||||
|
int p4 = Unsafe.Add(ref bufferBase, sourceOffset + 4); |
||||
|
int p5 = Unsafe.Add(ref bufferBase, sourceOffset + BufferStride); |
||||
|
int p6 = Unsafe.Add(ref bufferBase, sourceOffset + (2 * BufferStride)); |
||||
|
for (int pixel = 0; pixel < 8; pixel++) |
||||
|
{ |
||||
|
int tapOffset = pixel * 7; |
||||
|
int prediction = |
||||
|
(Unsafe.Add(ref taps, tapOffset) * p0) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 1) * p1) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 2) * p2) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 3) * p3) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 4) * p4) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 5) * p5) |
||||
|
+ (Unsafe.Add(ref taps, tapOffset + 6) * p6); |
||||
|
|
||||
|
int destinationOffset = ((row + (pixel >> 2)) * BufferStride) + column + (pixel & 3); |
||||
|
Unsafe.Add(ref bufferBase, destinationOffset) = (short)Math.Clamp((prediction + 8) >> 4, 0, maximum); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies an 8-bit recursive workspace into the strided destination.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The completed recursive workspace.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void CopyToDestination(ReadOnlySpan<byte> buffer, Span<byte> destination, int destinationStride, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo(destination.Slice(row * destinationStride, width)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies a high-bit-depth recursive workspace into the strided destination.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The completed recursive workspace.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void CopyToDestination(ReadOnlySpan<short> buffer, Span<short> destination, int destinationStride, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo(destination.Slice(row * destinationStride, width)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies an 8-bit recursive workspace into the strided destination without vectorized span copying.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The completed recursive workspace.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void CopyToDestinationScalar(ReadOnlySpan<byte> buffer, Span<byte> destination, int destinationStride, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int sourceOffset = ((row + 1) * BufferStride) + 1; |
||||
|
int destinationOffset = row * destinationStride; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
destination[destinationOffset + column] = buffer[sourceOffset + column]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Copies a high-bit-depth recursive workspace into the strided destination without vectorized span copying.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The completed recursive workspace.</param>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
private static void CopyToDestinationScalar(ReadOnlySpan<short> buffer, Span<short> destination, int destinationStride, int width, int height) |
||||
|
{ |
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
int sourceOffset = ((row + 1) * BufferStride) + 1; |
||||
|
int destinationOffset = row * destinationStride; |
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
destination[destinationOffset + column] = buffer[sourceOffset + column]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the coefficient set for one AV1 filter-intra prediction mode.
|
||||
|
/// </summary>
|
||||
|
internal interface IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the filter-intra mode implemented by the operator.
|
||||
|
/// </summary>
|
||||
|
public static abstract Av1FilterIntraMode Mode { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the eight seven-tap coefficient rows used by the operator.
|
||||
|
/// </summary>
|
||||
|
public static abstract ReadOnlySpan<sbyte> Taps { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the AV1 Paeth filter-intra coefficient operator.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct PaethOperator : IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Paeth; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static ReadOnlySpan<sbyte> Taps => |
||||
|
[ |
||||
|
-12, 14, 0, 0, 0, 14, 0, |
||||
|
-10, 0, 14, 0, 0, 12, 0, |
||||
|
-9, 0, 0, 14, 0, 11, 0, |
||||
|
-8, 0, 0, 0, 14, 10, 0, |
||||
|
-10, 12, 0, 0, 0, 0, 14, |
||||
|
-9, 1, 12, 0, 0, 0, 12, |
||||
|
-8, 0, 0, 12, 0, 1, 11, |
||||
|
-7, 0, 0, 1, 12, 1, 9, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the AV1 vertical filter-intra coefficient operator.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct VerticalOperator : IAv1FilterIntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1FilterIntraMode Mode => Av1FilterIntraMode.Vertical; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static ReadOnlySpan<sbyte> Taps => |
||||
|
[ |
||||
|
-10, 16, 0, 0, 0, 10, 0, |
||||
|
-6, 0, 16, 0, 0, 6, 0, |
||||
|
-4, 0, 0, 16, 0, 4, 0, |
||||
|
-2, 0, 0, 0, 16, 2, 0, |
||||
|
-10, 16, 0, 0, 0, 0, 10, |
||||
|
-6, 0, 16, 0, 0, 0, 6, |
||||
|
-4, 0, 0, 16, 0, 0, 4, |
||||
|
-2, 0, 0, 0, 16, 0, 2, |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
@ -1,191 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces AV1 filter intra predictions from reconstructed neighboring samples.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// The scalar prediction follows the filter intra prediction process in section 7.11.2.3 of the AV1 specification.
|
|
||||
/// </remarks>
|
|
||||
internal static class Av1FilterIntraPredictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The row stride of the temporary prediction buffer.
|
|
||||
/// </summary>
|
|
||||
private const int BufferStride = 33; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of samples in the temporary prediction buffer.
|
|
||||
/// </summary>
|
|
||||
private const int BufferLength = BufferStride * BufferStride; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of nonzero filter coefficients used to predict each sample.
|
|
||||
/// </summary>
|
|
||||
private const int TapsPerPixel = 7; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of samples produced by each filter coefficient group.
|
|
||||
/// </summary>
|
|
||||
private const int PixelsPerGroup = 8; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of stored coefficients for each filter intra mode.
|
|
||||
/// </summary>
|
|
||||
private const int TapsPerMode = TapsPerPixel * PixelsPerGroup; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the filter coefficients for the five AV1 filter intra modes.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// AV1 defines eight filters per mode over seven nonzero neighboring samples. The eighth coefficient used by
|
|
||||
/// libaom is always zero, so it is omitted here to keep the scalar coefficient layout aligned with the work performed.
|
|
||||
/// </remarks>
|
|
||||
public static readonly sbyte[] Taps = |
|
||||
[ |
|
||||
|
|
||||
// DC
|
|
||||
-6, 10, 0, 0, 0, 12, 0, |
|
||||
-5, 2, 10, 0, 0, 9, 0, |
|
||||
-3, 1, 1, 10, 0, 7, 0, |
|
||||
-3, 1, 1, 2, 10, 5, 0, |
|
||||
-4, 6, 0, 0, 0, 2, 12, |
|
||||
-3, 2, 6, 0, 0, 2, 9, |
|
||||
-3, 2, 2, 6, 0, 2, 7, |
|
||||
-3, 1, 2, 2, 6, 3, 5, |
|
||||
|
|
||||
// Vertical
|
|
||||
-10, 16, 0, 0, 0, 10, 0, |
|
||||
-6, 0, 16, 0, 0, 6, 0, |
|
||||
-4, 0, 0, 16, 0, 4, 0, |
|
||||
-2, 0, 0, 0, 16, 2, 0, |
|
||||
-10, 16, 0, 0, 0, 0, 10, |
|
||||
-6, 0, 16, 0, 0, 0, 6, |
|
||||
-4, 0, 0, 16, 0, 0, 4, |
|
||||
-2, 0, 0, 0, 16, 0, 2, |
|
||||
|
|
||||
// Horizontal
|
|
||||
-8, 8, 0, 0, 0, 16, 0, |
|
||||
-8, 0, 8, 0, 0, 16, 0, |
|
||||
-8, 0, 0, 8, 0, 16, 0, |
|
||||
-8, 0, 0, 0, 8, 16, 0, |
|
||||
-4, 4, 0, 0, 0, 0, 16, |
|
||||
-4, 0, 4, 0, 0, 0, 16, |
|
||||
-4, 0, 0, 4, 0, 0, 16, |
|
||||
-4, 0, 0, 0, 4, 0, 16, |
|
||||
|
|
||||
// Directional 157 degrees
|
|
||||
-2, 8, 0, 0, 0, 10, 0, |
|
||||
-1, 3, 8, 0, 0, 6, 0, |
|
||||
-1, 2, 3, 8, 0, 4, 0, |
|
||||
0, 1, 2, 3, 8, 2, 0, |
|
||||
-1, 4, 0, 0, 0, 3, 10, |
|
||||
-1, 3, 4, 0, 0, 4, 6, |
|
||||
-1, 2, 3, 4, 0, 4, 4, |
|
||||
-1, 2, 2, 3, 4, 3, 3, |
|
||||
|
|
||||
// Paeth
|
|
||||
-12, 14, 0, 0, 0, 14, 0, |
|
||||
-10, 0, 14, 0, 0, 12, 0, |
|
||||
-9, 0, 0, 14, 0, 11, 0, |
|
||||
-8, 0, 0, 0, 14, 10, 0, |
|
||||
-10, 12, 0, 0, 0, 0, 14, |
|
||||
-9, 1, 12, 0, 0, 0, 12, |
|
||||
-8, 0, 0, 12, 0, 1, 11, |
|
||||
-7, 0, 0, 1, 12, 1, 9, |
|
||||
]; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an 8-bit filter intra prediction for a transform block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="mode">The filter intra mode whose coefficient set is applied.</param>
|
|
||||
public static void Predict( |
|
||||
Span<byte> destination, |
|
||||
nuint destinationStride, |
|
||||
Av1TransformSize transformSize, |
|
||||
Span<byte> above, |
|
||||
Span<byte> left, |
|
||||
Av1FilterIntraMode mode) |
|
||||
{ |
|
||||
int width = transformSize.GetWidth(); |
|
||||
int height = transformSize.GetHeight(); |
|
||||
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width)); |
|
||||
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height)); |
|
||||
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination)); |
|
||||
Guard.MustBeSizedAtLeast(above, width, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(left, height, nameof(left)); |
|
||||
|
|
||||
Span<byte> buffer = stackalloc byte[BufferLength]; |
|
||||
ref byte bufferRef = ref buffer[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
|
|
||||
// Row zero includes the top-left sample followed by the top neighbors.
|
|
||||
// Column zero stores the left neighbors so each 4-by-2 group can consume the
|
|
||||
// seven already-reconstructed samples defined by the recursive AV1 process.
|
|
||||
bufferRef = Unsafe.Subtract(ref aboveRef, 1); |
|
||||
above[..width].CopyTo(buffer[1..]); |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Unsafe.Add(ref bufferRef, (row + 1) * BufferStride) = Unsafe.Add(ref leftRef, row); |
|
||||
} |
|
||||
|
|
||||
int modeOffset = (int)mode * TapsPerMode; |
|
||||
ref sbyte tapsRef = ref Taps[modeOffset]; |
|
||||
for (int row = 1; row <= height; row += 2) |
|
||||
{ |
|
||||
for (int column = 1; column <= width; column += 4) |
|
||||
{ |
|
||||
int sourceOffset = ((row - 1) * BufferStride) + column - 1; |
|
||||
int p0 = Unsafe.Add(ref bufferRef, sourceOffset); |
|
||||
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1); |
|
||||
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2); |
|
||||
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3); |
|
||||
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4); |
|
||||
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + BufferStride); |
|
||||
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * BufferStride)); |
|
||||
|
|
||||
for (int pixel = 0; pixel < PixelsPerGroup; pixel++) |
|
||||
{ |
|
||||
int tapOffset = pixel * TapsPerPixel; |
|
||||
int prediction = |
|
||||
(Unsafe.Add(ref tapsRef, tapOffset) * p0) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6); |
|
||||
|
|
||||
int rowOffset = pixel >> 2; |
|
||||
int columnOffset = pixel & 3; |
|
||||
int destinationOffset = ((row + rowOffset) * BufferStride) + column + columnOffset; |
|
||||
Unsafe.Add(ref bufferRef, destinationOffset) = |
|
||||
(byte)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, 255); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
buffer.Slice(((row + 1) * BufferStride) + 1, width).CopyTo( |
|
||||
MemoryMarshal.CreateSpan(ref destinationRef, width)); |
|
||||
|
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, destinationStride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,121 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs AV1 filter-intra prediction blocks from prepared neighboring samples.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The implementation follows the recursive filter-intra process in section 7.11.2.3 of the AV1 specification.
|
||||
|
/// </remarks>
|
||||
|
internal abstract partial class Av1FilterIntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The row stride of the recursive prediction workspace.
|
||||
|
/// </summary>
|
||||
|
public const int BufferStride = 33; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The number of samples in the recursive prediction workspace.
|
||||
|
/// </summary>
|
||||
|
public const int ScratchLength = BufferStride * BufferStride; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The DC filter-intra predictor.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1FilterIntraPredictor<DcOperator> DcPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical filter-intra predictor.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1FilterIntraPredictor<VerticalOperator> VerticalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal filter-intra predictor.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1FilterIntraPredictor<HorizontalOperator> HorizontalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The 157-degree directional filter-intra predictor.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1FilterIntraPredictor<Directional157Operator> Directional157Predictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Paeth filter-intra predictor.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1FilterIntraPredictor<PaethOperator> PaethPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the filter-intra mode implemented by this predictor.
|
||||
|
/// </summary>
|
||||
|
public abstract Av1FilterIntraMode Mode { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the closed predictor for a filter-intra mode.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mode">The decoded filter-intra mode.</param>
|
||||
|
/// <returns>The predictor for <paramref name="mode"/>.</returns>
|
||||
|
public static Av1FilterIntraPredictorBase GetPredictor(Av1FilterIntraMode mode) |
||||
|
=> mode switch |
||||
|
{ |
||||
|
Av1FilterIntraMode.DC => DcPredictor, |
||||
|
Av1FilterIntraMode.Vertical => VerticalPredictor, |
||||
|
Av1FilterIntraMode.Horizontal => HorizontalPredictor, |
||||
|
Av1FilterIntraMode.Directional157 => Directional157Predictor, |
||||
|
Av1FilterIntraMode.Paeth => PaethPredictor, |
||||
|
_ => throw new InvalidImageContentException($"Filter-intra mode {mode} is not defined by AV1."), |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit filter-intra block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
|
||||
|
public abstract void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth filter-intra block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
||||
|
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
|
||||
|
public abstract void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit filter-intra block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
|
||||
|
public abstract void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height, Span<byte> scratch); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth filter-intra block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width.</param>
|
||||
|
/// <param name="height">The block height.</param>
|
||||
|
/// <param name="bitDepth">The reconstructed sample precision.</param>
|
||||
|
/// <param name="scratch">The caller-owned recursive prediction workspace.</param>
|
||||
|
public abstract void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height, int bitDepth, Span<short> scratch); |
||||
|
} |
||||
@ -1,602 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Implements AV1 intra prediction for 10-bit and 12-bit sample buffers.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// Samples are stored in signed 16-bit buffers, but predictions are clamped to the nonnegative range of the signaled bit depth.
|
|
||||
/// </remarks>
|
|
||||
internal static class Av1HighBitDepthPredictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The row stride of the temporary filter intra prediction buffer.
|
|
||||
/// </summary>
|
|
||||
private const int FilterBufferStride = 33; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of samples in the temporary filter intra prediction buffer.
|
|
||||
/// </summary>
|
|
||||
private const int FilterBufferLength = FilterBufferStride * FilterBufferStride; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of nonzero filter coefficients used to predict each filter intra sample.
|
|
||||
/// </summary>
|
|
||||
private const int FilterTapsPerPixel = 7; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of samples produced by each filter coefficient group.
|
|
||||
/// </summary>
|
|
||||
private const int FilterPixelsPerGroup = 8; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of stored coefficients for each filter intra mode.
|
|
||||
/// </summary>
|
|
||||
private const int FilterTapsPerMode = FilterTapsPerPixel * FilterPixelsPerGroup; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a high-bit-depth DC intra prediction from the available neighboring samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="hasLeft">A value indicating whether reconstructed left samples are available.</param>
|
|
||||
/// <param name="hasAbove">A value indicating whether reconstructed top samples are available.</param>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
public static void DcPredictor( |
|
||||
bool hasLeft, |
|
||||
bool hasAbove, |
|
||||
Av1TransformSize transformSize, |
|
||||
Span<short> destination, |
|
||||
nuint destinationStride, |
|
||||
Span<short> above, |
|
||||
Span<short> left, |
|
||||
int bitDepth) |
|
||||
{ |
|
||||
int width = transformSize.GetWidth(); |
|
||||
int height = transformSize.GetHeight(); |
|
||||
int sum = 0; |
|
||||
int count = 0; |
|
||||
|
|
||||
if (hasAbove) |
|
||||
{ |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
sum += above[column]; |
|
||||
} |
|
||||
|
|
||||
count += width; |
|
||||
} |
|
||||
|
|
||||
if (hasLeft) |
|
||||
{ |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
sum += left[row]; |
|
||||
} |
|
||||
|
|
||||
count += height; |
|
||||
} |
|
||||
|
|
||||
short prediction = count == 0 |
|
||||
? (short)(1 << (bitDepth - 1)) |
|
||||
: (short)((sum + (count >> 1)) / count); |
|
||||
|
|
||||
// The midpoint is normative when neither edge exists; otherwise the half-count bias
|
|
||||
// rounds the mean of the available top and left samples to the nearest integer.
|
|
||||
Fill(destination, destinationStride, width, height, prediction); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a high-bit-depth nondirectional intra prediction.
|
|
||||
/// </summary>
|
|
||||
/// <param name="mode">The nondirectional prediction mode to apply.</param>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
public static void GeneralPredictor( |
|
||||
Av1PredictionMode mode, |
|
||||
Av1TransformSize transformSize, |
|
||||
Span<short> destination, |
|
||||
nuint destinationStride, |
|
||||
Span<short> above, |
|
||||
Span<short> left) |
|
||||
{ |
|
||||
int width = transformSize.GetWidth(); |
|
||||
int height = transformSize.GetHeight(); |
|
||||
|
|
||||
switch (mode) |
|
||||
{ |
|
||||
case Av1PredictionMode.Horizontal: |
|
||||
PredictHorizontal(destination, destinationStride, left, width, height); |
|
||||
break; |
|
||||
case Av1PredictionMode.Vertical: |
|
||||
PredictVertical(destination, destinationStride, above, width, height); |
|
||||
break; |
|
||||
case Av1PredictionMode.Paeth: |
|
||||
PredictPaeth(destination, destinationStride, above, left, width, height); |
|
||||
break; |
|
||||
case Av1PredictionMode.Smooth: |
|
||||
PredictSmooth(destination, destinationStride, above, left, width, height); |
|
||||
break; |
|
||||
case Av1PredictionMode.SmoothHorizontal: |
|
||||
PredictSmoothHorizontal(destination, destinationStride, above, left, width, height); |
|
||||
break; |
|
||||
case Av1PredictionMode.SmoothVertical: |
|
||||
PredictSmoothVertical(destination, destinationStride, above, left, width, height); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a high-bit-depth directional intra prediction.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="upsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="angle">The prediction angle in degrees.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
public static void DirectionalPredictor( |
|
||||
Span<short> destination, |
|
||||
nuint destinationStride, |
|
||||
Av1TransformSize transformSize, |
|
||||
Span<short> above, |
|
||||
Span<short> left, |
|
||||
bool upsampleAbove, |
|
||||
bool upsampleLeft, |
|
||||
int angle, |
|
||||
int bitDepth) |
|
||||
{ |
|
||||
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle)); |
|
||||
int dx = Av1PredictorFactory.GetDeltaX(angle); |
|
||||
int dy = Av1PredictorFactory.GetDeltaY(angle); |
|
||||
int width = transformSize.GetWidth(); |
|
||||
int height = transformSize.GetHeight(); |
|
||||
|
|
||||
// Angles on a cardinal axis copy one reference edge directly. Other angles are
|
|
||||
// separated into the three AV1 projection zones according to the edges they cross.
|
|
||||
if (angle is > 0 and < 90) |
|
||||
{ |
|
||||
PredictDirectionalZone1(destination, destinationStride, above, upsampleAbove, dx, width, height, bitDepth); |
|
||||
} |
|
||||
else if (angle is > 90 and < 180) |
|
||||
{ |
|
||||
PredictDirectionalZone2(destination, destinationStride, above, left, upsampleAbove, upsampleLeft, dx, dy, width, height, bitDepth); |
|
||||
} |
|
||||
else if (angle is > 180 and < 270) |
|
||||
{ |
|
||||
PredictDirectionalZone3(destination, destinationStride, left, upsampleLeft, dx, dy, width, height, bitDepth); |
|
||||
} |
|
||||
else if (angle == 90) |
|
||||
{ |
|
||||
PredictVertical(destination, destinationStride, above, width, height); |
|
||||
} |
|
||||
else if (angle == 180) |
|
||||
{ |
|
||||
PredictHorizontal(destination, destinationStride, left, width, height); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a high-bit-depth filter intra prediction.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The transform size that determines the prediction block dimensions.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="mode">The filter intra mode whose coefficient set is applied.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
public static void FilterIntraPredictor( |
|
||||
Span<short> destination, |
|
||||
nuint destinationStride, |
|
||||
Av1TransformSize transformSize, |
|
||||
Span<short> above, |
|
||||
Span<short> left, |
|
||||
Av1FilterIntraMode mode, |
|
||||
int bitDepth) |
|
||||
{ |
|
||||
int width = transformSize.GetWidth(); |
|
||||
int height = transformSize.GetHeight(); |
|
||||
int maximum = (1 << bitDepth) - 1; |
|
||||
DebugGuard.MustBeLessThanOrEqualTo(width, 32, nameof(width)); |
|
||||
DebugGuard.MustBeLessThanOrEqualTo(height, 32, nameof(height)); |
|
||||
Guard.MustBeGreaterThanOrEqualTo(destinationStride, (nuint)width, nameof(destinationStride)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)destinationStride * height, nameof(destination)); |
|
||||
Guard.MustBeSizedAtLeast(above, width, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(left, height, nameof(left)); |
|
||||
|
|
||||
Span<short> buffer = stackalloc short[FilterBufferLength]; |
|
||||
ref short bufferRef = ref buffer[0]; |
|
||||
ref short aboveRef = ref above[0]; |
|
||||
ref short leftRef = ref left[0]; |
|
||||
|
|
||||
// Row zero includes the top-left sample followed by the top neighbors.
|
|
||||
// Column zero stores the left neighbors so each 4-by-2 group can consume the
|
|
||||
// seven already-reconstructed samples defined by the recursive AV1 process.
|
|
||||
bufferRef = Unsafe.Subtract(ref aboveRef, 1); |
|
||||
above[..width].CopyTo(buffer[1..]); |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Unsafe.Add(ref bufferRef, (row + 1) * FilterBufferStride) = Unsafe.Add(ref leftRef, row); |
|
||||
} |
|
||||
|
|
||||
int modeOffset = (int)mode * FilterTapsPerMode; |
|
||||
ref sbyte tapsRef = ref Av1FilterIntraPredictor.Taps[modeOffset]; |
|
||||
for (int row = 1; row <= height; row += 2) |
|
||||
{ |
|
||||
for (int column = 1; column <= width; column += 4) |
|
||||
{ |
|
||||
int sourceOffset = ((row - 1) * FilterBufferStride) + column - 1; |
|
||||
int p0 = Unsafe.Add(ref bufferRef, sourceOffset); |
|
||||
int p1 = Unsafe.Add(ref bufferRef, sourceOffset + 1); |
|
||||
int p2 = Unsafe.Add(ref bufferRef, sourceOffset + 2); |
|
||||
int p3 = Unsafe.Add(ref bufferRef, sourceOffset + 3); |
|
||||
int p4 = Unsafe.Add(ref bufferRef, sourceOffset + 4); |
|
||||
int p5 = Unsafe.Add(ref bufferRef, sourceOffset + FilterBufferStride); |
|
||||
int p6 = Unsafe.Add(ref bufferRef, sourceOffset + (2 * FilterBufferStride)); |
|
||||
|
|
||||
for (int pixel = 0; pixel < FilterPixelsPerGroup; pixel++) |
|
||||
{ |
|
||||
int tapOffset = pixel * FilterTapsPerPixel; |
|
||||
int prediction = |
|
||||
(Unsafe.Add(ref tapsRef, tapOffset) * p0) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 1) * p1) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 2) * p2) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 3) * p3) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 4) * p4) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 5) * p5) |
|
||||
+ (Unsafe.Add(ref tapsRef, tapOffset + 6) * p6); |
|
||||
|
|
||||
int rowOffset = pixel >> 2; |
|
||||
int columnOffset = pixel & 3; |
|
||||
int destinationOffset = ((row + rowOffset) * FilterBufferStride) + column + columnOffset; |
|
||||
Unsafe.Add(ref bufferRef, destinationOffset) = |
|
||||
(short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 4), 0, maximum); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
buffer.Slice(((row + 1) * FilterBufferStride) + 1, width).CopyTo( |
|
||||
destination.Slice(row * (int)destinationStride, width)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Copies each left reference sample across one destination row.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictHorizontal(Span<short> destination, nuint stride, Span<short> left, int width, int height) |
|
||||
{ |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
destination.Slice(row * (int)stride, width).Fill(left[row]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Copies the top reference samples into every destination row.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictVertical(Span<short> destination, nuint stride, Span<short> above, int width, int height) |
|
||||
{ |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
above[..width].CopyTo(destination.Slice(row * (int)stride, width)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a Paeth prediction from the nearest top, left, and top-left reference sample.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictPaeth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
|
||||
{ |
|
||||
int topLeft = Unsafe.Subtract(ref above[0], 1); |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
short leftValue = left[row]; |
|
||||
short topValue = above[column]; |
|
||||
int basis = topValue + leftValue - topLeft; |
|
||||
int leftDistance = Av1Math.AbsoluteDifference(basis, leftValue); |
|
||||
int topDistance = Av1Math.AbsoluteDifference(basis, topValue); |
|
||||
int topLeftDistance = Av1Math.AbsoluteDifference(basis, topLeft); |
|
||||
|
|
||||
// The comparison order preserves AV1's left, top, then top-left tie precedence.
|
|
||||
destinationRow[column] = leftDistance <= topDistance && leftDistance <= topLeftDistance |
|
||||
? leftValue |
|
||||
: topDistance <= topLeftDistance ? topValue : (short)topLeft; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a two-dimensional smooth prediction from the four terminating edge samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictSmooth(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
|
||||
{ |
|
||||
int below = left[height - 1]; |
|
||||
int right = above[width - 1]; |
|
||||
Span<int> widthWeights = Av1SmoothPredictor.Weights.AsSpan(width, width); |
|
||||
Span<int> heightWeights = Av1SmoothPredictor.Weights.AsSpan(height, height); |
|
||||
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
|
||||
int log2Scale = Av1SmoothPredictor.WeightLog2Scale + 1; |
|
||||
|
|
||||
// Horizontal weights follow the block width and vertical weights follow the height.
|
|
||||
// Keeping those domains separate is required for rectangular transform blocks.
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
int rowWeight = heightWeights[row]; |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
int columnWeight = widthWeights[column]; |
|
||||
int prediction = (above[column] * rowWeight) + (below * (scale - rowWeight)); |
|
||||
prediction += (left[row] * columnWeight) + (right * (scale - columnWeight)); |
|
||||
destinationRow[column] = (short)Av1Math.DivideRound(prediction, log2Scale); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a horizontal smooth prediction between the left and right edge samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictSmoothHorizontal(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
|
||||
{ |
|
||||
int right = above[width - 1]; |
|
||||
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(width, width); |
|
||||
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
|
||||
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
int weight = weights[column]; |
|
||||
int prediction = (left[row] * weight) + (right * (scale - weight)); |
|
||||
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces a vertical smooth prediction between the top and bottom edge samples.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The reconstructed top reference samples.</param>
|
|
||||
/// <param name="left">The reconstructed left reference samples.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
private static void PredictSmoothVertical(Span<short> destination, nuint stride, Span<short> above, Span<short> left, int width, int height) |
|
||||
{ |
|
||||
int below = left[height - 1]; |
|
||||
Span<int> weights = Av1SmoothPredictor.Weights.AsSpan(height, height); |
|
||||
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
|
||||
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
int weight = weights[row]; |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
int prediction = (above[column] * weight) + (below * (scale - weight)); |
|
||||
destinationRow[column] = (short)Av1Math.DivideRound(prediction, Av1SmoothPredictor.WeightLog2Scale); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Projects top reference samples into a directional zone 1 prediction block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsample">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
private static void PredictDirectionalZone1(Span<short> destination, nuint stride, Span<short> above, bool upsample, int dx, int width, int height, int bitDepth) |
|
||||
{ |
|
||||
int upsampleAbove = upsample ? 1 : 0; |
|
||||
int maxBasisX = (width + height - 1) << upsampleAbove; |
|
||||
int fractionBitCount = 6 - upsampleAbove; |
|
||||
int basisIncrement = 1 << upsampleAbove; |
|
||||
int maximum = (1 << bitDepth) - 1; |
|
||||
int x = dx; |
|
||||
ref short aboveRef = ref above[0]; |
|
||||
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
|
|
||||
// AV1 retains six fractional projection bits, or five after reference upsampling.
|
|
||||
// The interpolation weights sum to 32 because the low projection bit is discarded.
|
|
||||
int basis = x >> fractionBitCount; |
|
||||
int shift = ((x << upsampleAbove) & 0x3F) >> 1; |
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
if (basis < maxBasisX) |
|
||||
{ |
|
||||
int prediction = (Unsafe.Add(ref aboveRef, basis) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basis + 1) * shift); |
|
||||
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
destinationRow[column] = Unsafe.Add(ref aboveRef, maxBasisX); |
|
||||
} |
|
||||
|
|
||||
basis += basisIncrement; |
|
||||
} |
|
||||
|
|
||||
x += dx; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Projects top and left reference samples into a directional zone 2 prediction block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top reference samples, including any required extension.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="doUpsampleAbove">A value indicating whether the top reference samples were upsampled.</param>
|
|
||||
/// <param name="doUpsampleLeft">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
private static void PredictDirectionalZone2(Span<short> destination, nuint stride, Span<short> above, Span<short> left, bool doUpsampleAbove, bool doUpsampleLeft, int dx, int dy, int width, int height, int bitDepth) |
|
||||
{ |
|
||||
int upsampleAbove = doUpsampleAbove ? 1 : 0; |
|
||||
int upsampleLeft = doUpsampleLeft ? 1 : 0; |
|
||||
int minBasisX = -(1 << upsampleAbove); |
|
||||
int fractionBitCountX = 6 - upsampleAbove; |
|
||||
int fractionBitCountY = 6 - upsampleLeft; |
|
||||
int basisIncrementX = 1 << upsampleAbove; |
|
||||
int maximum = (1 << bitDepth) - 1; |
|
||||
int x = -dx; |
|
||||
ref short aboveRef = ref above[0]; |
|
||||
ref short leftRef = ref left[0]; |
|
||||
|
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
Span<short> destinationRow = destination.Slice(row * (int)stride, width); |
|
||||
int basisX = x >> fractionBitCountX; |
|
||||
int y = (row << 6) - dy; |
|
||||
for (int column = 0; column < width; column++, basisX += basisIncrementX, y -= dy) |
|
||||
{ |
|
||||
int prediction; |
|
||||
|
|
||||
// A nonnegative top projection uses the above edge. Once the projection crosses
|
|
||||
// the top-left corner, the same destination sample is projected from the left edge.
|
|
||||
if (basisX >= minBasisX) |
|
||||
{ |
|
||||
int shift = ((x * (1 << upsampleAbove)) & 0x3F) >> 1; |
|
||||
prediction = (Unsafe.Add(ref aboveRef, basisX) * (32 - shift)) + (Unsafe.Add(ref aboveRef, basisX + 1) * shift); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
int basisY = y >> fractionBitCountY; |
|
||||
int shift = ((y * (1 << upsampleLeft)) & 0x3F) >> 1; |
|
||||
prediction = (Unsafe.Add(ref leftRef, basisY) * (32 - shift)) + (Unsafe.Add(ref leftRef, basisY + 1) * shift); |
|
||||
} |
|
||||
|
|
||||
destinationRow[column] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
|
||||
} |
|
||||
|
|
||||
x -= dx; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Projects left reference samples into a directional zone 3 prediction block.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the predicted samples.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="left">The left reference samples, including any required extension.</param>
|
|
||||
/// <param name="upsample">A value indicating whether the left reference samples were upsampled.</param>
|
|
||||
/// <param name="dx">The horizontal projection derivative, which must be one in zone 3.</param>
|
|
||||
/// <param name="dy">The vertical projection derivative in Q6 precision.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
/// <param name="bitDepth">The number of bits used to represent each sample.</param>
|
|
||||
private static void PredictDirectionalZone3(Span<short> destination, nuint stride, Span<short> left, bool upsample, int dx, int dy, int width, int height, int bitDepth) |
|
||||
{ |
|
||||
int upsampleLeft = upsample ? 1 : 0; |
|
||||
int maxBasisY = (width + height - 1) << upsampleLeft; |
|
||||
int fractionBitCount = 6 - upsampleLeft; |
|
||||
int basisIncrement = 1 << upsampleLeft; |
|
||||
int maximum = (1 << bitDepth) - 1; |
|
||||
int y = dy; |
|
||||
ref short leftRef = ref left[0]; |
|
||||
Guard.IsTrue(dx == 1, nameof(dx), "Dx expected to be always equal to 1 for directional Zone 3 prediction."); |
|
||||
|
|
||||
for (int column = 0; column < width; column++) |
|
||||
{ |
|
||||
// Zone 3 is the transpose of zone 1: columns advance along the projected left edge,
|
|
||||
// while rows advance through the reference samples for each destination column.
|
|
||||
int basis = y >> fractionBitCount; |
|
||||
int shift = ((y << upsampleLeft) & 0x3F) >> 1; |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
int destinationOffset = (row * (int)stride) + column; |
|
||||
if (basis < maxBasisY) |
|
||||
{ |
|
||||
int prediction = (Unsafe.Add(ref leftRef, basis) * (32 - shift)) + (Unsafe.Add(ref leftRef, basis + 1) * shift); |
|
||||
destination[destinationOffset] = (short)Av1Math.Clamp(Av1Math.RoundPowerOf2(prediction, 5), 0, maximum); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
destination[destinationOffset] = Unsafe.Add(ref leftRef, maxBasisY); |
|
||||
} |
|
||||
|
|
||||
basis += basisIncrement; |
|
||||
} |
|
||||
|
|
||||
y += dy; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Fills a rectangular prediction block with one sample value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The buffer that receives the sample value.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="width">The width of the prediction block in samples.</param>
|
|
||||
/// <param name="height">The height of the prediction block in samples.</param>
|
|
||||
/// <param name="value">The sample value written to every destination position.</param>
|
|
||||
private static void Fill(Span<short> destination, nuint stride, int width, int height, short value) |
|
||||
{ |
|
||||
for (int row = 0; row < height; row++) |
|
||||
{ |
|
||||
destination.Slice(row * (int)stride, width).Fill(value); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by extending each left neighboring sample across its destination row.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1HorizontalPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples consumed and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1HorizontalPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1HorizontalPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1HorizontalPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1HorizontalPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block by extending its left edge horizontally.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The unused top-neighbor buffer required by the common predictor signature.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1HorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <remarks>SVT-AV1: <c>highbd_h_predictor</c>.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
uint width = (uint)this.blockWidth; |
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
Unsafe.InitBlock(ref destinationRef, Unsafe.Add(ref leftRef, r), width); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements AV1 horizontal intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct HorizontalOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.Horizontal; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs => Av1IntraPredictionInputs.Left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) => left; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,344 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Applies one closed non-directional AV1 prediction operator using the widest available SIMD width.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TOperator">The prediction-mode-specific arithmetic.</typeparam>
|
||||
|
internal sealed class Av1IntraPredictor<TOperator> : Av1IntraPredictorBase |
||||
|
where TOperator : struct, IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public override Av1PredictionMode Mode => TOperator.Mode; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
||||
|
{ |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref byte topBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref byte leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]); |
||||
|
|
||||
|
Av1IntraPredictionInputs inputs = TOperator.Inputs; |
||||
|
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0; |
||||
|
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0; |
||||
|
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0; |
||||
|
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0; |
||||
|
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0; |
||||
|
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0; |
||||
|
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0; |
||||
|
|
||||
|
byte topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default; |
||||
|
byte topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default; |
||||
|
byte bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default; |
||||
|
int processedColumns = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int vectorizedColumns = width - (width % Vector512<byte>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector512<byte> topLeftVector = usesTopLeft ? Vector512.Create(topLeft) : default; |
||||
|
Vector512<byte> topRightVector = usesTopRight ? Vector512.Create(topRight) : default; |
||||
|
Vector512<byte> bottomLeftVector = usesBottomLeft ? Vector512.Create(bottomLeft) : default; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector512<byte> leftVector = usesLeft ? Vector512.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = 0; column < vectorizedColumns; column += Vector512<byte>.Count) |
||||
|
{ |
||||
|
Vector512<byte> top = usesTop ? Vector512.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector512<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = vectorizedColumns; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int remainingColumns = width - processedColumns; |
||||
|
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<byte>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector256<byte> topLeftVector = usesTopLeft ? Vector256.Create(topLeft) : default; |
||||
|
Vector256<byte> topRightVector = usesTopRight ? Vector256.Create(topRight) : default; |
||||
|
Vector256<byte> bottomLeftVector = usesBottomLeft ? Vector256.Create(bottomLeft) : default; |
||||
|
int endColumn = processedColumns + vectorizedColumns; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector256<byte> leftVector = usesLeft ? Vector256.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < endColumn; column += Vector256<byte>.Count) |
||||
|
{ |
||||
|
Vector256<byte> top = usesTop ? Vector256.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector256<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = endColumn; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int remainingColumns = width - processedColumns; |
||||
|
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<byte>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector128<byte> topLeftVector = usesTopLeft ? Vector128.Create(topLeft) : default; |
||||
|
Vector128<byte> topRightVector = usesTopRight ? Vector128.Create(topRight) : default; |
||||
|
Vector128<byte> bottomLeftVector = usesBottomLeft ? Vector128.Create(bottomLeft) : default; |
||||
|
int endColumn = processedColumns + vectorizedColumns; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector128<byte> leftVector = usesLeft ? Vector128.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < endColumn; column += Vector128<byte>.Count) |
||||
|
{ |
||||
|
Vector128<byte> top = usesTop ? Vector128.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector128<byte> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = endColumn; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// AV1 dimensions are multiples of four. The tail is normally zero on SIMD hardware, but retaining the
|
||||
|
// scalar continuation keeps the traversal correct when intrinsics are disabled by FeatureTestRunner.
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
byte leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < width; column++) |
||||
|
{ |
||||
|
byte top = usesTop ? Unsafe.Add(ref topBase, column) : default; |
||||
|
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0; |
||||
|
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height) |
||||
|
{ |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref short topBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref short leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]); |
||||
|
|
||||
|
Av1IntraPredictionInputs inputs = TOperator.Inputs; |
||||
|
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0; |
||||
|
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0; |
||||
|
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0; |
||||
|
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0; |
||||
|
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0; |
||||
|
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0; |
||||
|
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0; |
||||
|
|
||||
|
short topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default; |
||||
|
short topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default; |
||||
|
short bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default; |
||||
|
int processedColumns = 0; |
||||
|
|
||||
|
if (Vector512.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int vectorizedColumns = width - (width % Vector512<short>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector512<short> topLeftVector = usesTopLeft ? Vector512.Create(topLeft) : default; |
||||
|
Vector512<short> topRightVector = usesTopRight ? Vector512.Create(topRight) : default; |
||||
|
Vector512<short> bottomLeftVector = usesBottomLeft ? Vector512.Create(bottomLeft) : default; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector512<short> leftVector = usesLeft ? Vector512.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = 0; column < vectorizedColumns; column += Vector512<short>.Count) |
||||
|
{ |
||||
|
Vector512<short> top = usesTop ? Vector512.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector512<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = vectorizedColumns; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector256.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int remainingColumns = width - processedColumns; |
||||
|
int vectorizedColumns = remainingColumns - (remainingColumns % Vector256<short>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector256<short> topLeftVector = usesTopLeft ? Vector256.Create(topLeft) : default; |
||||
|
Vector256<short> topRightVector = usesTopRight ? Vector256.Create(topRight) : default; |
||||
|
Vector256<short> bottomLeftVector = usesBottomLeft ? Vector256.Create(bottomLeft) : default; |
||||
|
int endColumn = processedColumns + vectorizedColumns; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector256<short> leftVector = usesLeft ? Vector256.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < endColumn; column += Vector256<short>.Count) |
||||
|
{ |
||||
|
Vector256<short> top = usesTop ? Vector256.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector256<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = endColumn; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (Vector128.IsHardwareAccelerated) |
||||
|
{ |
||||
|
int remainingColumns = width - processedColumns; |
||||
|
int vectorizedColumns = remainingColumns - (remainingColumns % Vector128<short>.Count); |
||||
|
if (vectorizedColumns > 0) |
||||
|
{ |
||||
|
Vector128<short> topLeftVector = usesTopLeft ? Vector128.Create(topLeft) : default; |
||||
|
Vector128<short> topRightVector = usesTopRight ? Vector128.Create(topRight) : default; |
||||
|
Vector128<short> bottomLeftVector = usesBottomLeft ? Vector128.Create(bottomLeft) : default; |
||||
|
int endColumn = processedColumns + vectorizedColumns; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
Vector128<short> leftVector = usesLeft ? Vector128.Create(Unsafe.Add(ref leftBase, row)) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < endColumn; column += Vector128<short>.Count) |
||||
|
{ |
||||
|
Vector128<short> top = usesTop ? Vector128.LoadUnsafe(ref topBase, (nuint)column) : default; |
||||
|
ref int columnWeights = ref usesColumnWeight ? ref Unsafe.Add(ref columnWeightBase, column) : ref columnWeightBase; |
||||
|
Vector128<short> prediction = TOperator.Predict(top, leftVector, topLeftVector, topRightVector, bottomLeftVector, ref columnWeights, rowWeight); |
||||
|
prediction.StoreUnsafe(ref destinationRow, (nuint)column); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
processedColumns = endColumn; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
short leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = processedColumns; column < width; column++) |
||||
|
{ |
||||
|
short top = usesTop ? Unsafe.Add(ref topBase, column) : default; |
||||
|
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0; |
||||
|
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height) |
||||
|
{ |
||||
|
ref byte destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref byte topBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref byte leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]); |
||||
|
|
||||
|
Av1IntraPredictionInputs inputs = TOperator.Inputs; |
||||
|
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0; |
||||
|
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0; |
||||
|
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0; |
||||
|
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0; |
||||
|
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0; |
||||
|
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0; |
||||
|
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0; |
||||
|
byte topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default; |
||||
|
byte topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default; |
||||
|
byte bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
byte leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref byte destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
byte top = usesTop ? Unsafe.Add(ref topBase, column) : default; |
||||
|
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0; |
||||
|
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height) |
||||
|
{ |
||||
|
ref short destinationBase = ref MemoryMarshal.GetReference(destination); |
||||
|
ref short topBase = ref MemoryMarshal.GetReference(above); |
||||
|
ref short leftBase = ref MemoryMarshal.GetReference(left); |
||||
|
ref int columnWeightBase = ref MemoryMarshal.GetReference(SmoothWeights[width..]); |
||||
|
|
||||
|
Av1IntraPredictionInputs inputs = TOperator.Inputs; |
||||
|
bool usesTop = (inputs & Av1IntraPredictionInputs.Top) != 0; |
||||
|
bool usesLeft = (inputs & Av1IntraPredictionInputs.Left) != 0; |
||||
|
bool usesTopLeft = (inputs & Av1IntraPredictionInputs.TopLeft) != 0; |
||||
|
bool usesTopRight = (inputs & Av1IntraPredictionInputs.TopRight) != 0; |
||||
|
bool usesBottomLeft = (inputs & Av1IntraPredictionInputs.BottomLeft) != 0; |
||||
|
bool usesColumnWeight = (inputs & Av1IntraPredictionInputs.ColumnWeight) != 0; |
||||
|
bool usesRowWeight = (inputs & Av1IntraPredictionInputs.RowWeight) != 0; |
||||
|
short topLeft = usesTopLeft ? Unsafe.Subtract(ref topBase, 1) : default; |
||||
|
short topRight = usesTopRight ? Unsafe.Add(ref topBase, width - 1) : default; |
||||
|
short bottomLeft = usesBottomLeft ? Unsafe.Add(ref leftBase, height - 1) : default; |
||||
|
|
||||
|
for (int row = 0; row < height; row++) |
||||
|
{ |
||||
|
short leftSample = usesLeft ? Unsafe.Add(ref leftBase, row) : default; |
||||
|
int rowWeight = usesRowWeight ? SmoothWeights[height + row] : 0; |
||||
|
ref short destinationRow = ref Unsafe.Add(ref destinationBase, row * destinationStride); |
||||
|
|
||||
|
for (int column = 0; column < width; column++) |
||||
|
{ |
||||
|
short top = usesTop ? Unsafe.Add(ref topBase, column) : default; |
||||
|
int columnWeight = usesColumnWeight ? Unsafe.Add(ref columnWeightBase, column) : 0; |
||||
|
Unsafe.Add(ref destinationRow, column) = TOperator.Predict(top, leftSample, topLeft, topRight, bottomLeft, columnWeight, rowWeight); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies the neighboring inputs consumed by an AV1 intra-prediction operator.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
internal enum Av1IntraPredictionInputs |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The operator does not consume neighboring samples.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes samples from the top reference.
|
||||
|
/// </summary>
|
||||
|
Top = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes samples from the left reference.
|
||||
|
/// </summary>
|
||||
|
Left = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes the shared top-left reference.
|
||||
|
/// </summary>
|
||||
|
TopLeft = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes the final top reference.
|
||||
|
/// </summary>
|
||||
|
TopRight = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes the final left reference.
|
||||
|
/// </summary>
|
||||
|
BottomLeft = 16, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes the horizontal smooth weights.
|
||||
|
/// </summary>
|
||||
|
ColumnWeight = 32, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The operator consumes the vertical smooth weights.
|
||||
|
/// </summary>
|
||||
|
RowWeight = 64, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the scalar and SIMD arithmetic for one non-directional AV1 intra-prediction mode.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Each overload performs the same lane-wise operation. The generic predictor traversal selects the widest
|
||||
|
/// available overload, and the JIT specializes each static interface call for the closed operator type.
|
||||
|
/// </remarks>
|
||||
|
internal interface IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the prediction mode implemented by the operator.
|
||||
|
/// </summary>
|
||||
|
public static abstract Av1PredictionMode Mode { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the neighboring inputs consumed by the operator.
|
||||
|
/// </summary>
|
||||
|
public static abstract Av1IntraPredictionInputs Inputs { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one 8-bit sample when hardware vectorization is unavailable.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference sample.</param>
|
||||
|
/// <param name="left">The left reference sample.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample.</param>
|
||||
|
/// <param name="topRight">The final top reference sample.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample.</param>
|
||||
|
/// <param name="columnWeight">The horizontal Q8 smooth weight.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted sample.</returns>
|
||||
|
public static abstract byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts sixteen 8-bit samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts thirty-two 8-bit samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts sixty-four 8-bit samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts one high-bit-depth sample when hardware vectorization is unavailable.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference sample.</param>
|
||||
|
/// <param name="left">The left reference sample.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample.</param>
|
||||
|
/// <param name="topRight">The final top reference sample.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample.</param>
|
||||
|
/// <param name="columnWeight">The horizontal Q8 smooth weight.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted sample.</returns>
|
||||
|
public static abstract short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts eight high-bit-depth samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts sixteen high-bit-depth samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts thirty-two high-bit-depth samples in parallel.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top reference samples.</param>
|
||||
|
/// <param name="left">The left reference sample in every lane.</param>
|
||||
|
/// <param name="topLeft">The shared top-left reference sample in every lane.</param>
|
||||
|
/// <param name="topRight">The final top reference sample in every lane.</param>
|
||||
|
/// <param name="bottomLeft">The final left reference sample in every lane.</param>
|
||||
|
/// <param name="columnWeights">The first horizontal Q8 smooth weight for these lanes.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 smooth weight.</param>
|
||||
|
/// <returns>The predicted samples.</returns>
|
||||
|
public static abstract Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,166 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens AV1 sample vectors for fixed-point prediction arithmetic and narrows completed results.
|
||||
|
/// </summary>
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Widens sixteen 8-bit samples into four 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first four widened samples.</param>
|
||||
|
/// <param name="result1">The second four widened samples.</param>
|
||||
|
/// <param name="result2">The third four widened samples.</param>
|
||||
|
/// <param name="result3">The fourth four widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector128<byte> source, out Vector128<int> result0, out Vector128<int> result1, out Vector128<int> result2, out Vector128<int> result3) |
||||
|
{ |
||||
|
(Vector128<ushort> low, Vector128<ushort> high) = Vector128.Widen(source); |
||||
|
(Vector128<uint> low0, Vector128<uint> low1) = Vector128.Widen(low); |
||||
|
(Vector128<uint> high0, Vector128<uint> high1) = Vector128.Widen(high); |
||||
|
result0 = low0.AsInt32(); |
||||
|
result1 = low1.AsInt32(); |
||||
|
result2 = high0.AsInt32(); |
||||
|
result3 = high1.AsInt32(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens thirty-two 8-bit samples into four 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first eight widened samples.</param>
|
||||
|
/// <param name="result1">The second eight widened samples.</param>
|
||||
|
/// <param name="result2">The third eight widened samples.</param>
|
||||
|
/// <param name="result3">The fourth eight widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector256<byte> source, out Vector256<int> result0, out Vector256<int> result1, out Vector256<int> result2, out Vector256<int> result3) |
||||
|
{ |
||||
|
(Vector256<ushort> low, Vector256<ushort> high) = Vector256.Widen(source); |
||||
|
(Vector256<uint> low0, Vector256<uint> low1) = Vector256.Widen(low); |
||||
|
(Vector256<uint> high0, Vector256<uint> high1) = Vector256.Widen(high); |
||||
|
result0 = low0.AsInt32(); |
||||
|
result1 = low1.AsInt32(); |
||||
|
result2 = high0.AsInt32(); |
||||
|
result3 = high1.AsInt32(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens sixty-four 8-bit samples into four 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first sixteen widened samples.</param>
|
||||
|
/// <param name="result1">The second sixteen widened samples.</param>
|
||||
|
/// <param name="result2">The third sixteen widened samples.</param>
|
||||
|
/// <param name="result3">The fourth sixteen widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector512<byte> source, out Vector512<int> result0, out Vector512<int> result1, out Vector512<int> result2, out Vector512<int> result3) |
||||
|
{ |
||||
|
(Vector512<ushort> low, Vector512<ushort> high) = Vector512.Widen(source); |
||||
|
(Vector512<uint> low0, Vector512<uint> low1) = Vector512.Widen(low); |
||||
|
(Vector512<uint> high0, Vector512<uint> high1) = Vector512.Widen(high); |
||||
|
result0 = low0.AsInt32(); |
||||
|
result1 = low1.AsInt32(); |
||||
|
result2 = high0.AsInt32(); |
||||
|
result3 = high1.AsInt32(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens eight high-bit-depth samples into two 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first four widened samples.</param>
|
||||
|
/// <param name="result1">The second four widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector128<short> source, out Vector128<int> result0, out Vector128<int> result1) |
||||
|
=> (result0, result1) = Vector128.Widen(source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens sixteen high-bit-depth samples into two 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first eight widened samples.</param>
|
||||
|
/// <param name="result1">The second eight widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector256<short> source, out Vector256<int> result0, out Vector256<int> result1) |
||||
|
=> (result0, result1) = Vector256.Widen(source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Widens thirty-two high-bit-depth samples into two 32-bit vectors.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The packed samples.</param>
|
||||
|
/// <param name="result0">The first sixteen widened samples.</param>
|
||||
|
/// <param name="result1">The second sixteen widened samples.</param>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static void Widen(Vector512<short> source, out Vector512<int> result0, out Vector512<int> result1) |
||||
|
=> (result0, result1) = Vector512.Widen(source); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows four 32-bit vectors into sixteen 8-bit samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first four samples.</param>
|
||||
|
/// <param name="source1">The second four samples.</param>
|
||||
|
/// <param name="source2">The third four samples.</param>
|
||||
|
/// <param name="source3">The fourth four samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Narrow(Vector128<int> source0, Vector128<int> source1, Vector128<int> source2, Vector128<int> source3) |
||||
|
=> Vector128.Narrow(Vector128.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector128.Narrow(source2.AsUInt32(), source3.AsUInt32())); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows four 32-bit vectors into thirty-two 8-bit samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first eight samples.</param>
|
||||
|
/// <param name="source1">The second eight samples.</param>
|
||||
|
/// <param name="source2">The third eight samples.</param>
|
||||
|
/// <param name="source3">The fourth eight samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Narrow(Vector256<int> source0, Vector256<int> source1, Vector256<int> source2, Vector256<int> source3) |
||||
|
=> Vector256.Narrow(Vector256.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector256.Narrow(source2.AsUInt32(), source3.AsUInt32())); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows four 32-bit vectors into sixty-four 8-bit samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first sixteen samples.</param>
|
||||
|
/// <param name="source1">The second sixteen samples.</param>
|
||||
|
/// <param name="source2">The third sixteen samples.</param>
|
||||
|
/// <param name="source3">The fourth sixteen samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Narrow(Vector512<int> source0, Vector512<int> source1, Vector512<int> source2, Vector512<int> source3) |
||||
|
=> Vector512.Narrow(Vector512.Narrow(source0.AsUInt32(), source1.AsUInt32()), Vector512.Narrow(source2.AsUInt32(), source3.AsUInt32())); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows two 32-bit vectors into eight high-bit-depth samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first four samples.</param>
|
||||
|
/// <param name="source1">The second four samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Narrow(Vector128<int> source0, Vector128<int> source1) => Vector128.Narrow(source0, source1); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows two 32-bit vectors into sixteen high-bit-depth samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first eight samples.</param>
|
||||
|
/// <param name="source1">The second eight samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Narrow(Vector256<int> source0, Vector256<int> source1) => Vector256.Narrow(source0, source1); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Narrows two 32-bit vectors into thirty-two high-bit-depth samples.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source0">The first sixteen samples.</param>
|
||||
|
/// <param name="source1">The second sixteen samples.</param>
|
||||
|
/// <returns>The packed samples.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Narrow(Vector512<int> source0, Vector512<int> source1) => Vector512.Narrow(source0, source1); |
||||
|
} |
||||
@ -0,0 +1,209 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements AV1 Paeth intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct PaethOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.Paeth; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs |
||||
|
=> Av1IntraPredictionInputs.Top | Av1IntraPredictionInputs.Left | Av1IntraPredictionInputs.TopLeft; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) |
||||
|
{ |
||||
|
int basis = top + left - topLeft; |
||||
|
int distanceLeft = Math.Abs(basis - left); |
||||
|
int distanceTop = Math.Abs(basis - top); |
||||
|
int distanceTopLeft = Math.Abs(basis - topLeft); |
||||
|
|
||||
|
// AV1 resolves equal distances in left, top, top-left order.
|
||||
|
return distanceLeft <= distanceTop && distanceLeft <= distanceTopLeft ? left : distanceTop <= distanceTopLeft ? top : topLeft; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) |
||||
|
{ |
||||
|
int basis = top + left - topLeft; |
||||
|
int distanceLeft = Math.Abs(basis - left); |
||||
|
int distanceTop = Math.Abs(basis - top); |
||||
|
int distanceTopLeft = Math.Abs(basis - topLeft); |
||||
|
|
||||
|
// High-bit-depth prediction has the same tie order as the 8-bit process.
|
||||
|
return distanceLeft <= distanceTop && distanceLeft <= distanceTopLeft ? left : distanceTop <= distanceTopLeft ? top : topLeft; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
=> PredictPaeth(top, left, topLeft); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for sixteen 8-bit lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<byte> PredictPaeth(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft) |
||||
|
{ |
||||
|
// The Paeth distances to left and top simplify to |top - topLeft| and |left - topLeft|. The established
|
||||
|
// PNG predictor uses the same byte-lane identity, avoiding four widening stages for every sixteen samples.
|
||||
|
Vector128<byte> topMinusTopLeft = Vector128.SubtractSaturate(top, topLeft); |
||||
|
Vector128<byte> leftMinusTopLeft = Vector128.SubtractSaturate(left, topLeft); |
||||
|
Vector128<byte> distanceLeft = Vector128.SubtractSaturate(topLeft, top) | topMinusTopLeft; |
||||
|
Vector128<byte> distanceTop = Vector128.SubtractSaturate(topLeft, left) | leftMinusTopLeft; |
||||
|
Vector128<byte> sameDirection = Vector128.Equals(Vector128.Equals(topMinusTopLeft, Vector128<byte>.Zero), Vector128.Equals(leftMinusTopLeft, Vector128<byte>.Zero)); |
||||
|
Vector128<byte> distanceTopLeft = sameDirection | Vector128.SubtractSaturate(distanceTop, distanceLeft) | Vector128.SubtractSaturate(distanceLeft, distanceTop); |
||||
|
Vector128<byte> minimumTopTopLeft = Vector128.Min(distanceTopLeft, distanceTop); |
||||
|
Vector128<byte> topOrTopLeft = Vector128.ConditionalSelect(Vector128.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for thirty-two 8-bit lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<byte> PredictPaeth(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft) |
||||
|
{ |
||||
|
Vector256<byte> topMinusTopLeft = Vector256.SubtractSaturate(top, topLeft); |
||||
|
Vector256<byte> leftMinusTopLeft = Vector256.SubtractSaturate(left, topLeft); |
||||
|
Vector256<byte> distanceLeft = Vector256.SubtractSaturate(topLeft, top) | topMinusTopLeft; |
||||
|
Vector256<byte> distanceTop = Vector256.SubtractSaturate(topLeft, left) | leftMinusTopLeft; |
||||
|
Vector256<byte> sameDirection = Vector256.Equals(Vector256.Equals(topMinusTopLeft, Vector256<byte>.Zero), Vector256.Equals(leftMinusTopLeft, Vector256<byte>.Zero)); |
||||
|
Vector256<byte> distanceTopLeft = sameDirection | Vector256.SubtractSaturate(distanceTop, distanceLeft) | Vector256.SubtractSaturate(distanceLeft, distanceTop); |
||||
|
Vector256<byte> minimumTopTopLeft = Vector256.Min(distanceTopLeft, distanceTop); |
||||
|
Vector256<byte> topOrTopLeft = Vector256.ConditionalSelect(Vector256.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for sixty-four 8-bit lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<byte> PredictPaeth(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft) |
||||
|
{ |
||||
|
Vector512<byte> topMinusTopLeft = Vector512.SubtractSaturate(top, topLeft); |
||||
|
Vector512<byte> leftMinusTopLeft = Vector512.SubtractSaturate(left, topLeft); |
||||
|
Vector512<byte> distanceLeft = Vector512.SubtractSaturate(topLeft, top) | topMinusTopLeft; |
||||
|
Vector512<byte> distanceTop = Vector512.SubtractSaturate(topLeft, left) | leftMinusTopLeft; |
||||
|
Vector512<byte> sameDirection = Vector512.Equals(Vector512.Equals(topMinusTopLeft, Vector512<byte>.Zero), Vector512.Equals(leftMinusTopLeft, Vector512<byte>.Zero)); |
||||
|
Vector512<byte> distanceTopLeft = sameDirection | Vector512.SubtractSaturate(distanceTop, distanceLeft) | Vector512.SubtractSaturate(distanceLeft, distanceTop); |
||||
|
Vector512<byte> minimumTopTopLeft = Vector512.Min(distanceTopLeft, distanceTop); |
||||
|
Vector512<byte> topOrTopLeft = Vector512.ConditionalSelect(Vector512.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for eight high-bit-depth lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<short> PredictPaeth(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft) |
||||
|
{ |
||||
|
// AV1 samples are at most twelve bits. Both signed differences and their sum are therefore bounded by
|
||||
|
// 8190, allowing the exact Paeth distances to stay in signed 16-bit lanes without widening.
|
||||
|
Vector128<short> topDelta = top - topLeft; |
||||
|
Vector128<short> leftDelta = left - topLeft; |
||||
|
Vector128<short> distanceLeft = Vector128.Abs(topDelta); |
||||
|
Vector128<short> distanceTop = Vector128.Abs(leftDelta); |
||||
|
Vector128<short> distanceTopLeft = Vector128.Abs(topDelta + leftDelta); |
||||
|
|
||||
|
Vector128<short> minimumTopTopLeft = Vector128.Min(distanceTopLeft, distanceTop); |
||||
|
Vector128<short> topOrTopLeft = Vector128.ConditionalSelect(Vector128.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector128.ConditionalSelect(Vector128.Equals(Vector128.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for sixteen high-bit-depth lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<short> PredictPaeth(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft) |
||||
|
{ |
||||
|
Vector256<short> topDelta = top - topLeft; |
||||
|
Vector256<short> leftDelta = left - topLeft; |
||||
|
Vector256<short> distanceLeft = Vector256.Abs(topDelta); |
||||
|
Vector256<short> distanceTop = Vector256.Abs(leftDelta); |
||||
|
Vector256<short> distanceTopLeft = Vector256.Abs(topDelta + leftDelta); |
||||
|
|
||||
|
Vector256<short> minimumTopTopLeft = Vector256.Min(distanceTopLeft, distanceTop); |
||||
|
Vector256<short> topOrTopLeft = Vector256.ConditionalSelect(Vector256.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector256.ConditionalSelect(Vector256.Equals(Vector256.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the nearest Paeth reference for thirty-two high-bit-depth lanes.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The top candidates.</param>
|
||||
|
/// <param name="left">The left candidates.</param>
|
||||
|
/// <param name="topLeft">The top-left candidates.</param>
|
||||
|
/// <returns>The selected candidates.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<short> PredictPaeth(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft) |
||||
|
{ |
||||
|
Vector512<short> topDelta = top - topLeft; |
||||
|
Vector512<short> leftDelta = left - topLeft; |
||||
|
Vector512<short> distanceLeft = Vector512.Abs(topDelta); |
||||
|
Vector512<short> distanceTop = Vector512.Abs(leftDelta); |
||||
|
Vector512<short> distanceTopLeft = Vector512.Abs(topDelta + leftDelta); |
||||
|
|
||||
|
Vector512<short> minimumTopTopLeft = Vector512.Min(distanceTopLeft, distanceTop); |
||||
|
Vector512<short> topOrTopLeft = Vector512.ConditionalSelect(Vector512.Equals(minimumTopTopLeft, distanceTop), top, topLeft); |
||||
|
return Vector512.ConditionalSelect(Vector512.Equals(Vector512.Min(minimumTopTopLeft, distanceLeft), distanceLeft), left, topOrTopLeft); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements horizontal AV1 smooth intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct SmoothHorizontalOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Q8 scale used by the smooth surface.
|
||||
|
/// </summary>
|
||||
|
private const int WeightScale = 256; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.SmoothHorizontal; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs |
||||
|
=> Av1IntraPredictionInputs.Left | Av1IntraPredictionInputs.TopRight | Av1IntraPredictionInputs.ColumnWeight; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) |
||||
|
=> (byte)(((left * columnWeight) + (topRight * (WeightScale - columnWeight)) + (WeightScale >> 1)) >> 8); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector128<int> endpointBias = Vector128.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector128<int>.Count)), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector128<int>.Count)), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector256<int> endpointBias = Vector256.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector256<int>.Count)), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector256<int>.Count)), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector512<int> endpointBias = Vector512.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector512<int>.Count)), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector512<int>.Count)), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) |
||||
|
=> (short)(((left * columnWeight) + (topRight * (WeightScale - columnWeight)) + (WeightScale >> 1)) >> 8); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector128<int> endpointBias = Vector128.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector256<int> endpointBias = Vector256.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector512<int> endpointBias = Vector512.Create((topRightSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights), horizontalDelta, endpointBias), |
||||
|
Calculate(Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count), horizontalDelta, endpointBias)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates four horizontal smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Calculate(Vector128<int> columnWeights, int horizontalDelta, Vector128<int> endpointBias) |
||||
|
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates eight horizontal smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> Calculate(Vector256<int> columnWeights, int horizontalDelta, Vector256<int> endpointBias) |
||||
|
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates sixteen horizontal smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The top-right endpoint and rounding bias in every lane.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<int> Calculate(Vector512<int> columnWeights, int horizontalDelta, Vector512<int> endpointBias) |
||||
|
=> ((columnWeights * horizontalDelta) + endpointBias) >> 8; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,241 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements two-dimensional AV1 smooth intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct SmoothOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Q8 scale used by each smooth surface.
|
||||
|
/// </summary>
|
||||
|
private const int WeightScale = 256; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.Smooth; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs |
||||
|
=> Av1IntraPredictionInputs.Top |
||||
|
| Av1IntraPredictionInputs.Left |
||||
|
| Av1IntraPredictionInputs.TopRight |
||||
|
| Av1IntraPredictionInputs.BottomLeft |
||||
|
| Av1IntraPredictionInputs.ColumnWeight |
||||
|
| Av1IntraPredictionInputs.RowWeight; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) |
||||
|
{ |
||||
|
int prediction = (top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)); |
||||
|
prediction += (left * columnWeight) + (topRight * (WeightScale - columnWeight)); |
||||
|
return (byte)((prediction + WeightScale) >> 9); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector128<int> top0, out Vector128<int> top1, out Vector128<int> top2, out Vector128<int> top3); |
||||
|
Vector128<int> weight0 = Vector128.LoadUnsafe(ref columnWeights); |
||||
|
Vector128<int> weight1 = Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count); |
||||
|
Vector128<int> weight2 = Vector128.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector128<int>.Count)); |
||||
|
Vector128<int> weight3 = Vector128.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector128<int>.Count)); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample); |
||||
|
Vector128<int> endpointBias = Vector128.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector256<int> top0, out Vector256<int> top1, out Vector256<int> top2, out Vector256<int> top3); |
||||
|
Vector256<int> weight0 = Vector256.LoadUnsafe(ref columnWeights); |
||||
|
Vector256<int> weight1 = Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count); |
||||
|
Vector256<int> weight2 = Vector256.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector256<int>.Count)); |
||||
|
Vector256<int> weight3 = Vector256.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector256<int>.Count)); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample); |
||||
|
Vector256<int> endpointBias = Vector256.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector512<int> top0, out Vector512<int> top1, out Vector512<int> top2, out Vector512<int> top3); |
||||
|
Vector512<int> weight0 = Vector512.LoadUnsafe(ref columnWeights); |
||||
|
Vector512<int> weight1 = Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count); |
||||
|
Vector512<int> weight2 = Vector512.LoadUnsafe(ref columnWeights, (nuint)(2 * Vector512<int>.Count)); |
||||
|
Vector512<int> weight3 = Vector512.LoadUnsafe(ref columnWeights, (nuint)(3 * Vector512<int>.Count)); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample); |
||||
|
Vector512<int> endpointBias = Vector512.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top2, weight2, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top3, weight3, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) |
||||
|
{ |
||||
|
int prediction = (top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)); |
||||
|
prediction += (left * columnWeight) + (topRight * (WeightScale - columnWeight)); |
||||
|
return (short)((prediction + WeightScale) >> 9); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector128<int> top0, out Vector128<int> top1); |
||||
|
Vector128<int> weight0 = Vector128.LoadUnsafe(ref columnWeights); |
||||
|
Vector128<int> weight1 = Vector128.LoadUnsafe(ref columnWeights, (nuint)Vector128<int>.Count); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample); |
||||
|
Vector128<int> endpointBias = Vector128.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector256<int> top0, out Vector256<int> top1); |
||||
|
Vector256<int> weight0 = Vector256.LoadUnsafe(ref columnWeights); |
||||
|
Vector256<int> weight1 = Vector256.LoadUnsafe(ref columnWeights, (nuint)Vector256<int>.Count); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample); |
||||
|
Vector256<int> endpointBias = Vector256.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector512<int> top0, out Vector512<int> top1); |
||||
|
Vector512<int> weight0 = Vector512.LoadUnsafe(ref columnWeights); |
||||
|
Vector512<int> weight1 = Vector512.LoadUnsafe(ref columnWeights, (nuint)Vector512<int>.Count); |
||||
|
int leftSample = left.GetElement(0); |
||||
|
int topRightSample = topRight.GetElement(0); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
int horizontalDelta = leftSample - topRightSample; |
||||
|
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample); |
||||
|
Vector512<int> endpointBias = Vector512.Create(((bottomLeftSample + topRightSample) * WeightScale) + WeightScale); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, weight0, bottomLeftVector, horizontalDelta, endpointBias, rowWeight), |
||||
|
Calculate(top1, weight1, bottomLeftVector, horizontalDelta, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates four two-dimensional smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Calculate( |
||||
|
Vector128<int> top, |
||||
|
Vector128<int> columnWeights, |
||||
|
Vector128<int> bottomLeft, |
||||
|
int horizontalDelta, |
||||
|
Vector128<int> endpointBias, |
||||
|
int rowWeight) |
||||
|
{ |
||||
|
// Expanding the complementary weights gives the exact normative sum while reducing it to two vector
|
||||
|
// multiplications: (top - bottom) * rowWeight + (left - right) * columnWeight + the endpoint bias.
|
||||
|
return (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates eight two-dimensional smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> Calculate( |
||||
|
Vector256<int> top, |
||||
|
Vector256<int> columnWeights, |
||||
|
Vector256<int> bottomLeft, |
||||
|
int horizontalDelta, |
||||
|
Vector256<int> endpointBias, |
||||
|
int rowWeight) |
||||
|
=> (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates sixteen two-dimensional smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="columnWeights">The horizontal Q8 weights.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="horizontalDelta">The difference between the left sample and top-right endpoint.</param>
|
||||
|
/// <param name="endpointBias">The combined endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<int> Calculate( |
||||
|
Vector512<int> top, |
||||
|
Vector512<int> columnWeights, |
||||
|
Vector512<int> bottomLeft, |
||||
|
int horizontalDelta, |
||||
|
Vector512<int> endpointBias, |
||||
|
int rowWeight) |
||||
|
=> (((top - bottomLeft) * rowWeight) + (columnWeights * horizontalDelta) + endpointBias) >> 9; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,164 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements vertical AV1 smooth intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct SmoothVerticalOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The Q8 scale used by the smooth surface.
|
||||
|
/// </summary>
|
||||
|
private const int WeightScale = 256; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.SmoothVertical; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs |
||||
|
=> Av1IntraPredictionInputs.Top | Av1IntraPredictionInputs.BottomLeft | Av1IntraPredictionInputs.RowWeight; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) |
||||
|
=> (byte)(((top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)) + (WeightScale >> 1)) >> 8); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector128<int> top0, out Vector128<int> top1, out Vector128<int> top2, out Vector128<int> top3); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample); |
||||
|
Vector128<int> endpointBias = Vector128.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top2, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top3, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector256<int> top0, out Vector256<int> top1, out Vector256<int> top2, out Vector256<int> top3); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample); |
||||
|
Vector256<int> endpointBias = Vector256.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top2, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top3, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector512<int> top0, out Vector512<int> top1, out Vector512<int> top2, out Vector512<int> top3); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample); |
||||
|
Vector512<int> endpointBias = Vector512.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top2, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top3, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) |
||||
|
=> (short)(((top * rowWeight) + (bottomLeft * (WeightScale - rowWeight)) + (WeightScale >> 1)) >> 8); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector128<int> top0, out Vector128<int> top1); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector128<int> bottomLeftVector = Vector128.Create(bottomLeftSample); |
||||
|
Vector128<int> endpointBias = Vector128.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector256<int> top0, out Vector256<int> top1); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector256<int> bottomLeftVector = Vector256.Create(bottomLeftSample); |
||||
|
Vector256<int> endpointBias = Vector256.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) |
||||
|
{ |
||||
|
Widen(top, out Vector512<int> top0, out Vector512<int> top1); |
||||
|
int bottomLeftSample = bottomLeft.GetElement(0); |
||||
|
Vector512<int> bottomLeftVector = Vector512.Create(bottomLeftSample); |
||||
|
Vector512<int> endpointBias = Vector512.Create((bottomLeftSample * WeightScale) + (WeightScale >> 1)); |
||||
|
|
||||
|
return Narrow( |
||||
|
Calculate(top0, bottomLeftVector, endpointBias, rowWeight), |
||||
|
Calculate(top1, bottomLeftVector, endpointBias, rowWeight)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates four vertical smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector128<int> Calculate(Vector128<int> top, Vector128<int> bottomLeft, Vector128<int> endpointBias, int rowWeight) |
||||
|
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates eight vertical smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector256<int> Calculate(Vector256<int> top, Vector256<int> bottomLeft, Vector256<int> endpointBias, int rowWeight) |
||||
|
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Calculates sixteen vertical smooth predictions.
|
||||
|
/// </summary>
|
||||
|
/// <param name="top">The widened top samples.</param>
|
||||
|
/// <param name="bottomLeft">The bottom-left endpoint in every lane.</param>
|
||||
|
/// <param name="endpointBias">The bottom-left endpoint and rounding bias in every lane.</param>
|
||||
|
/// <param name="rowWeight">The vertical Q8 weight.</param>
|
||||
|
/// <returns>The rounded predictions.</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector512<int> Calculate(Vector512<int> top, Vector512<int> bottomLeft, Vector512<int> endpointBias, int rowWeight) |
||||
|
=> (((top - bottomLeft) * rowWeight) + endpointBias) >> 8; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.Intrinsics; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements AV1 vertical intra prediction for scalar and SIMD lanes.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct VerticalOperator : IAv1IntraPredictionOperator |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1PredictionMode Mode => Av1PredictionMode.Vertical; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public static Av1IntraPredictionInputs Inputs => Av1IntraPredictionInputs.Top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static byte Predict(byte top, byte left, byte topLeft, byte topRight, byte bottomLeft, int columnWeight, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<byte> Predict(Vector128<byte> top, Vector128<byte> left, Vector128<byte> topLeft, Vector128<byte> topRight, Vector128<byte> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<byte> Predict(Vector256<byte> top, Vector256<byte> left, Vector256<byte> topLeft, Vector256<byte> topRight, Vector256<byte> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<byte> Predict(Vector512<byte> top, Vector512<byte> left, Vector512<byte> topLeft, Vector512<byte> topRight, Vector512<byte> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static short Predict(short top, short left, short topLeft, short topRight, short bottomLeft, int columnWeight, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector128<short> Predict(Vector128<short> top, Vector128<short> left, Vector128<short> topLeft, Vector128<short> topRight, Vector128<short> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector256<short> Predict(Vector256<short> top, Vector256<short> left, Vector256<short> topLeft, Vector256<short> topRight, Vector256<short> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector512<short> Predict(Vector512<short> top, Vector512<short> left, Vector512<short> topLeft, Vector512<short> topRight, Vector512<short> bottomLeft, ref int columnWeights, int rowWeight) => top; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,129 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reconstructs non-directional AV1 intra-prediction blocks from prepared neighboring samples.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The implementation covers the non-directional prediction processes in section 7.11.2 of the AV1 specification.
|
||||
|
/// </remarks>
|
||||
|
internal abstract partial class Av1IntraPredictorBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The horizontal prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<HorizontalOperator> HorizontalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<VerticalOperator> VerticalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The Paeth prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<PaethOperator> PaethPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The two-dimensional smooth prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<SmoothOperator> SmoothPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The horizontal smooth prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<SmoothHorizontalOperator> SmoothHorizontalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The vertical smooth prediction operator.
|
||||
|
/// </summary>
|
||||
|
private static readonly Av1IntraPredictor<SmoothVerticalOperator> SmoothVerticalPredictor = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the Q8 smooth weights for every supported block dimension.
|
||||
|
/// </summary>
|
||||
|
private static ReadOnlySpan<int> SmoothWeights => |
||||
|
[ |
||||
|
|
||||
|
// The first two entries are unused because the smallest AV1 prediction dimension is four samples.
|
||||
|
0, 0, |
||||
|
255, 128, |
||||
|
255, 149, 85, 64, |
||||
|
255, 197, 146, 105, 73, 50, 37, 32, |
||||
|
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16, |
||||
|
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, |
||||
|
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8, |
||||
|
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156, |
||||
|
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69, |
||||
|
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15, |
||||
|
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, |
||||
|
]; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the prediction mode implemented by this predictor.
|
||||
|
/// </summary>
|
||||
|
public abstract Av1PredictionMode Mode { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the closed predictor for a non-directional AV1 prediction mode.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mode">The decoded non-directional prediction mode.</param>
|
||||
|
/// <returns>The predictor for <paramref name="mode"/>.</returns>
|
||||
|
public static Av1IntraPredictorBase GetPredictor(Av1PredictionMode mode) |
||||
|
=> mode switch |
||||
|
{ |
||||
|
Av1PredictionMode.Horizontal => HorizontalPredictor, |
||||
|
Av1PredictionMode.Vertical => VerticalPredictor, |
||||
|
Av1PredictionMode.Paeth => PaethPredictor, |
||||
|
Av1PredictionMode.Smooth => SmoothPredictor, |
||||
|
Av1PredictionMode.SmoothHorizontal => SmoothHorizontalPredictor, |
||||
|
Av1PredictionMode.SmoothVertical => SmoothVerticalPredictor, |
||||
|
_ => throw new InvalidImageContentException($"Prediction mode {mode} is not a non-directional AV1 mode."), |
||||
|
}; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public abstract void Predict(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth block.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public abstract void Predict(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts an 8-bit block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public abstract void PredictScalar(Span<byte> destination, int destinationStride, ReadOnlySpan<byte> above, ReadOnlySpan<byte> left, int width, int height); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Predicts a high-bit-depth block without hardware intrinsics.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination block origin.</param>
|
||||
|
/// <param name="destinationStride">The destination row stride in samples.</param>
|
||||
|
/// <param name="above">The prepared top reference, preceded in memory by the top-left sample.</param>
|
||||
|
/// <param name="left">The prepared left reference.</param>
|
||||
|
/// <param name="width">The block width in samples.</param>
|
||||
|
/// <param name="height">The block height in samples.</param>
|
||||
|
public abstract void PredictScalar(Span<short> destination, int destinationStride, ReadOnlySpan<short> above, ReadOnlySpan<short> left, int width, int height); |
||||
|
} |
||||
@ -1,99 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by selecting the top, left, or top-left neighbor with the smallest local gradient.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1PaethPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of top samples consumed and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples consumed and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly uint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1PaethPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1PaethPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)blockSize.Width; |
|
||||
this.blockHeight = (uint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1PaethPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1PaethPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (uint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (uint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block using the Paeth gradient selector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples, preceded in memory by the top-left sample.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1PaethPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
|
|
||||
// The caller preserves the top-left sample immediately before the top row.
|
|
||||
int yTopLeft = Unsafe.Subtract(ref aboveRef, 1); |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
for (nuint r = 0; r < this.blockHeight; r++) |
|
||||
{ |
|
||||
for (nuint c = 0; c < this.blockWidth; c++) |
|
||||
{ |
|
||||
Unsafe.Add(ref destinationRef, c) = PredictSingle( |
|
||||
Unsafe.Add(ref leftRef, r), |
|
||||
Unsafe.Add(ref aboveRef, c), |
|
||||
yTopLeft); |
|
||||
} |
|
||||
|
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the reference sample nearest to the planar estimate <paramref name="top"/> + <paramref name="left"/> - <paramref name="topLeft"/>.
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">The left reference sample.</param>
|
|
||||
/// <param name="top">The top reference sample.</param>
|
|
||||
/// <param name="topLeft">The shared top-left reference sample.</param>
|
|
||||
/// <returns>The reference sample with the smallest absolute distance from the planar estimate.</returns>
|
|
||||
private static byte PredictSingle(byte left, byte top, int topLeft) |
|
||||
{ |
|
||||
int basis = top + left - topLeft; |
|
||||
int pLeft = Av1Math.AbsoluteDifference(basis, left); |
|
||||
int pTop = Av1Math.AbsoluteDifference(basis, top); |
|
||||
int pTopLeft = Av1Math.AbsoluteDifference(basis, topLeft); |
|
||||
|
|
||||
// Ties prefer left, then top, matching AV1's normative Paeth selection order.
|
|
||||
return (byte)((pLeft <= pTop && pLeft <= pTopLeft) ? left : (pTop <= pTopLeft) ? top : topLeft); |
|
||||
} |
|
||||
} |
|
||||
@ -1,275 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the scalar 8-bit or high-bit-depth AV1 intra predictor for a decoded prediction mode.
|
|
||||
/// </summary>
|
|
||||
internal class Av1PredictorFactory |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The Q8 directional derivatives indexed by acute angle in degrees; zero entries represent angles AV1 does not signal.
|
|
||||
/// </summary>
|
|
||||
private static readonly int[] DirectionalIntraDerivative = [ |
|
||||
|
|
||||
// More evenly spread out angles and limited to 10-bit
|
|
||||
// Values that are 0 will never be used
|
|
||||
// Approx angle
|
|
||||
0, 0, 0, // 0
|
|
||||
1023, 0, 0, // 3, ...
|
|
||||
547, 0, 0, // 6, ...
|
|
||||
372, 0, 0, 0, 0, // 9, ...
|
|
||||
273, 0, 0, // 14, ...
|
|
||||
215, 0, 0, // 17, ...
|
|
||||
178, 0, 0, // 20, ...
|
|
||||
151, 0, 0, // 23, ... (113 & 203 are base angles)
|
|
||||
132, 0, 0, // 26, ...
|
|
||||
116, 0, 0, // 29, ...
|
|
||||
102, 0, 0, 0, // 32, ...
|
|
||||
90, 0, 0, // 36, ...
|
|
||||
80, 0, 0, // 39, ...
|
|
||||
71, 0, 0, // 42, ...
|
|
||||
64, 0, 0, // 45, ... (45 & 135 are base angles)
|
|
||||
57, 0, 0, // 48, ...
|
|
||||
51, 0, 0, // 51, ...
|
|
||||
45, 0, 0, 0, // 54, ...
|
|
||||
40, 0, 0, // 58, ...
|
|
||||
35, 0, 0, // 61, ...
|
|
||||
31, 0, 0, // 64, ...
|
|
||||
27, 0, 0, // 67, ... (67 & 157 are base angles)
|
|
||||
23, 0, 0, // 70, ...
|
|
||||
19, 0, 0, // 73, ...
|
|
||||
15, 0, 0, 0, 0, // 76, ...
|
|
||||
11, 0, 0, // 81, ...
|
|
||||
7, 0, 0, // 84, ...
|
|
||||
3, 0, 0, // 87, ...
|
|
||||
]; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit block from the average of whichever top and left neighbor edges are available.
|
|
||||
/// </summary>
|
|
||||
/// <param name="hasLeft">Whether the left neighboring column is available.</param>
|
|
||||
/// <param name="hasAbove">Whether the top neighboring row is available.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
public static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn) |
|
||||
{ |
|
||||
if (hasLeft) |
|
||||
{ |
|
||||
if (hasAbove) |
|
||||
{ |
|
||||
Av1DcPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Av1DcLeftPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (hasAbove) |
|
||||
{ |
|
||||
Av1DcTopPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
Av1DcFillPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a high-bit-depth block from the average of whichever top and left neighbor edges are available.
|
|
||||
/// </summary>
|
|
||||
/// <param name="hasLeft">Whether the left neighboring column is available.</param>
|
|
||||
/// <param name="hasAbove">Whether the top neighboring row is available.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
/// <param name="bitDepth">The coded sample bit depth used to select the midpoint when no edge is available.</param>
|
|
||||
public static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn, int bitDepth) |
|
||||
=> Av1HighBitDepthPredictor.DcPredictor(hasLeft, hasAbove, transformSize, destination, destinationStride, aboveRow, leftColumn, bitDepth); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit block by projecting reference-edge samples along a directional angle.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="aboveRow">The top and top-right reference samples.</param>
|
|
||||
/// <param name="leftColumn">The left and bottom-left reference samples.</param>
|
|
||||
/// <param name="upsampleAbove">Whether the top reference edge is stored at half-sample intervals.</param>
|
|
||||
/// <param name="upsampleLeft">Whether the left reference edge is stored at half-sample intervals.</param>
|
|
||||
/// <param name="angle">The prediction angle in degrees from 1 through 269.</param>
|
|
||||
/// <remarks>SVT-AV1: <c>svt_aom_highbd_dr_predictor</c>.</remarks>
|
|
||||
public static void DirectionalPredictor(Span<byte> destination, nuint stride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) |
|
||||
{ |
|
||||
int dx = GetDeltaX(angle); |
|
||||
int dy = GetDeltaY(angle); |
|
||||
int bw = transformSize.GetWidth(); |
|
||||
int bh = transformSize.GetHeight(); |
|
||||
Guard.MustBeBetweenOrEqualTo(angle, 1, 269, nameof(angle)); |
|
||||
|
|
||||
// The three open quadrants select which reference edge or edge pair the projected ray intersects.
|
|
||||
// Exact 90- and 180-degree modes reduce to copying the top row or left column without interpolation.
|
|
||||
if (angle is > 0 and < 90) |
|
||||
{ |
|
||||
Av1DirectionalZone1Predictor.PredictScalar(transformSize, destination, stride, aboveRow, upsampleAbove, dx); |
|
||||
} |
|
||||
else if (angle is > 90 and < 180) |
|
||||
{ |
|
||||
Av1DirectionalZone2Predictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn, upsampleAbove, upsampleLeft, dx, dy); |
|
||||
} |
|
||||
else if (angle is > 180 and < 270) |
|
||||
{ |
|
||||
Av1DirectionalZone3Predictor.PredictScalar(transformSize, destination, stride, leftColumn, upsampleLeft, dx, dy); |
|
||||
} |
|
||||
else if (angle == 90) |
|
||||
{ |
|
||||
Av1VerticalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn); |
|
||||
} |
|
||||
else if (angle == 180) |
|
||||
{ |
|
||||
Av1HorizontalPredictor.PredictScalar(transformSize, destination, stride, aboveRow, leftColumn); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a high-bit-depth block by projecting reference-edge samples along a directional angle.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="aboveRow">The top and top-right reference samples.</param>
|
|
||||
/// <param name="leftColumn">The left and bottom-left reference samples.</param>
|
|
||||
/// <param name="upsampleAbove">Whether the top reference edge is stored at half-sample intervals.</param>
|
|
||||
/// <param name="upsampleLeft">Whether the left reference edge is stored at half-sample intervals.</param>
|
|
||||
/// <param name="angle">The prediction angle in degrees from 1 through 269.</param>
|
|
||||
/// <param name="bitDepth">The coded sample bit depth used to clamp interpolated values.</param>
|
|
||||
public static void DirectionalPredictor(Span<short> destination, nuint stride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, bool upsampleAbove, bool upsampleLeft, int angle, int bitDepth) |
|
||||
=> Av1HighBitDepthPredictor.DirectionalPredictor(destination, stride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle, bitDepth); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit block using the selected AV1 filter-intra kernel.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
/// <param name="filterIntraMode">The filter-intra coefficient set.</param>
|
|
||||
public static void FilterIntraPredictor(Span<byte> destination, nuint destinationStride, Av1TransformSize transformSize, Span<byte> aboveRow, Span<byte> leftColumn, Av1FilterIntraMode filterIntraMode) |
|
||||
=> Av1FilterIntraPredictor.Predict(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a high-bit-depth block using the selected AV1 filter-intra kernel.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
/// <param name="filterIntraMode">The filter-intra coefficient set.</param>
|
|
||||
/// <param name="bitDepth">The coded sample bit depth used to clamp filtered values.</param>
|
|
||||
public static void FilterIntraPredictor(Span<short> destination, nuint destinationStride, Av1TransformSize transformSize, Span<short> aboveRow, Span<short> leftColumn, Av1FilterIntraMode filterIntraMode, int bitDepth) |
|
||||
=> Av1HighBitDepthPredictor.FilterIntraPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode, bitDepth); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects an 8-bit horizontal, vertical, Paeth, or smooth predictor for a non-directional mode.
|
|
||||
/// </summary>
|
|
||||
/// <param name="mode">The non-directional prediction mode.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
public static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<byte> destination, nuint destinationStride, Span<byte> aboveRow, Span<byte> leftColumn) |
|
||||
{ |
|
||||
switch (mode) |
|
||||
{ |
|
||||
case Av1PredictionMode.Horizontal: |
|
||||
Av1HorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
case Av1PredictionMode.Vertical: |
|
||||
Av1VerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
case Av1PredictionMode.Paeth: |
|
||||
Av1PaethPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
case Av1PredictionMode.Smooth: |
|
||||
Av1SmoothPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
case Av1PredictionMode.SmoothHorizontal: |
|
||||
Av1SmoothHorizontalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
case Av1PredictionMode.SmoothVertical: |
|
||||
Av1SmoothVerticalPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects a high-bit-depth horizontal, vertical, Paeth, or smooth predictor for a non-directional mode.
|
|
||||
/// </summary>
|
|
||||
/// <param name="mode">The non-directional prediction mode.</param>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="destinationStride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="aboveRow">The top neighboring samples.</param>
|
|
||||
/// <param name="leftColumn">The left neighboring samples.</param>
|
|
||||
public static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span<short> destination, nuint destinationStride, Span<short> aboveRow, Span<short> leftColumn) |
|
||||
=> Av1HighBitDepthPredictor.GeneralPredictor(mode, transformSize, destination, destinationStride, aboveRow, leftColumn); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the Q8 vertical displacement per unit horizontal displacement for a directional angle.
|
|
||||
/// </summary>
|
|
||||
/// <param name="angle">The prediction angle in degrees.</param>
|
|
||||
/// <returns>The Q8 vertical derivative, or one when the selected directional zone does not consume it.</returns>
|
|
||||
public static int GetDeltaY(int angle) |
|
||||
{ |
|
||||
if (angle is > 90 and < 180) |
|
||||
{ |
|
||||
return DirectionalIntraDerivative[angle - 90]; |
|
||||
} |
|
||||
else if (angle is > 180 and < 270) |
|
||||
{ |
|
||||
return DirectionalIntraDerivative[270 - angle]; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// Zones one and the exact horizontal/vertical modes never consume dy; one avoids a zero placeholder.
|
|
||||
return 1; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the Q8 horizontal displacement per unit vertical displacement for a directional angle.
|
|
||||
/// </summary>
|
|
||||
/// <param name="angle">The prediction angle in degrees.</param>
|
|
||||
/// <returns>The Q8 horizontal derivative, or one when the selected directional zone does not consume it.</returns>
|
|
||||
public static int GetDeltaX(int angle) |
|
||||
{ |
|
||||
if (angle is > 0 and < 90) |
|
||||
{ |
|
||||
return DirectionalIntraDerivative[angle]; |
|
||||
} |
|
||||
else if (angle is > 90 and < 180) |
|
||||
{ |
|
||||
return DirectionalIntraDerivative[180 - angle]; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// Zone three and the exact horizontal/vertical modes never consume dx; one avoids a zero placeholder.
|
|
||||
return 1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,89 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by smoothly blending each left neighbor toward the top-right reference sample.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1SmoothHorizontalPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of interpolation weights and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples consumed and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothHorizontalPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1SmoothHorizontalPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothHorizontalPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1SmoothHorizontalPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block using horizontal smooth interpolation.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples whose final value supplies the right endpoint.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1SmoothHorizontalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <remarks>SVT-AV1: <c>highbd_smooth_h_predictor</c>.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
|
|
||||
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockWidth]; |
|
||||
|
|
||||
int log2Scale = Av1SmoothPredictor.WeightLog2Scale; |
|
||||
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
|
||||
|
|
||||
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
for (nuint c = 0; c < this.blockWidth; ++c) |
|
||||
{ |
|
||||
int columnWeight = Unsafe.Add(ref weights, c); |
|
||||
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale)); |
|
||||
|
|
||||
// The Q8 weight decreases toward the right edge, shifting influence from left to top-right.
|
|
||||
int thisPredition = Unsafe.Add(ref leftRef, r) * columnWeight; |
|
||||
thisPredition += rightPrediction * (scale - columnWeight); |
|
||||
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
|
||||
} |
|
||||
|
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,135 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by blending top-to-bottom and left-to-right smooth interpolation surfaces.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1SmoothPredictor : IAv1Predictor |
|
||||
{ |
|
||||
// Weights are quadratic from '1' to '1 / BlockSize', scaled by
|
|
||||
// 2^sm_weight_log2_scale.
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of fractional bits in the normative smooth-prediction weights.
|
|
||||
/// </summary>
|
|
||||
internal static readonly int WeightLog2Scale = 8; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The concatenated smooth-weight sequences, addressed by using the block dimension as the sequence offset.
|
|
||||
/// </summary>
|
|
||||
internal static readonly int[] Weights = [ |
|
||||
|
|
||||
// Unused, because we always offset by bs, which is at least 2.
|
|
||||
0, 0, |
|
||||
|
|
||||
// bs = 2
|
|
||||
255, 128, |
|
||||
|
|
||||
// bs = 4
|
|
||||
255, 149, 85, 64, |
|
||||
|
|
||||
// bs = 8
|
|
||||
255, 197, 146, 105, 73, 50, 37, 32, |
|
||||
|
|
||||
// bs = 16
|
|
||||
255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16, |
|
||||
|
|
||||
// bs = 32
|
|
||||
255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, |
|
||||
66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8, |
|
||||
|
|
||||
// bs = 64
|
|
||||
255, 248, 240, 233, 225, 218, 210, 203, 196, 189, 182, 176, 169, 163, 156, |
|
||||
150, 144, 138, 133, 127, 121, 116, 111, 106, 101, 96, 91, 86, 82, 77, 73, 69, |
|
||||
65, 61, 57, 54, 50, 47, 44, 41, 38, 35, 32, 29, 27, 25, 22, 20, 18, 16, 15, |
|
||||
13, 12, 10, 9, 8, 7, 6, 6, 5, 5, 4, 4, 4, |
|
||||
]; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of top samples consumed and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of left samples consumed and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1SmoothPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1SmoothPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block by combining horizontal and vertical smooth interpolation.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples.</param>
|
|
||||
/// <param name="left">The left neighboring samples.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1SmoothPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <remarks>SVT-AV1: <c>highbd_smooth_predictor</c>.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
|
|
||||
int rightPrediction = Unsafe.Add(ref aboveRef, this.blockWidth - 1); // estimated by top-right pixel
|
|
||||
ref int heightWeights = ref Weights[(int)this.blockHeight]; |
|
||||
ref int widthWeights = ref Weights[(int)this.blockWidth]; |
|
||||
|
|
||||
// The two independent interpolation surfaces each use Q8 weights, so their combined sum has one extra scale bit.
|
|
||||
int log2Scale = 1 + WeightLog2Scale; |
|
||||
int scale = 1 << WeightLog2Scale; |
|
||||
|
|
||||
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
int rowWeight = Unsafe.Add(ref heightWeights, r); |
|
||||
Guard.MustBeGreaterThanOrEqualTo(scale, rowWeight, nameof(scale)); |
|
||||
for (nuint c = 0; c < this.blockWidth; ++c) |
|
||||
{ |
|
||||
int columnWeight = Unsafe.Add(ref widthWeights, c); |
|
||||
Guard.MustBeGreaterThanOrEqualTo(scale, columnWeight, nameof(scale)); |
|
||||
|
|
||||
// Blend top toward bottom-left and left toward top-right, then normalize their combined Q8 contributions.
|
|
||||
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight; |
|
||||
thisPredition += belowPrediction * (scale - rowWeight); |
|
||||
thisPredition += Unsafe.Add(ref leftRef, r) * columnWeight; |
|
||||
thisPredition += rightPrediction * (scale - columnWeight); |
|
||||
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
|
||||
} |
|
||||
|
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,87 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by smoothly blending each top neighbor toward the bottom-left reference sample.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1SmoothVerticalPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of top samples consumed and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of interpolation weights, left samples consumed, and destination rows written.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothVerticalPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1SmoothVerticalPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1SmoothVerticalPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1SmoothVerticalPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block using vertical smooth interpolation.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples.</param>
|
|
||||
/// <param name="left">The left neighboring samples whose final value supplies the bottom endpoint.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1SmoothVerticalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <remarks>SVT-AV1: <c>highbd_smooth_v_predictor</c>.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte leftRef = ref left[0]; |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
int belowPrediction = Unsafe.Add(ref leftRef, this.blockHeight - 1); // estimated by bottom-left pixel
|
|
||||
ref int weights = ref Av1SmoothPredictor.Weights[(int)this.blockHeight]; |
|
||||
|
|
||||
int log2Scale = Av1SmoothPredictor.WeightLog2Scale; |
|
||||
int scale = 1 << Av1SmoothPredictor.WeightLog2Scale; |
|
||||
|
|
||||
// sm_weights_sanity_checks(sm_weights_w, sm_weights_h, scale, log2_scale + 2);
|
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
int rowWeight = Unsafe.Add(ref weights, r); |
|
||||
for (nuint c = 0; c < this.blockWidth; ++c) |
|
||||
{ |
|
||||
// The Q8 weight decreases toward the bottom edge, shifting influence from top to bottom-left.
|
|
||||
int thisPredition = Unsafe.Add(ref aboveRef, c) * rowWeight; |
|
||||
thisPredition += belowPrediction * (scale - rowWeight); |
|
||||
Unsafe.Add(ref destinationRef, c) = (byte)Av1Math.DivideRound(thisPredition, log2Scale); |
|
||||
} |
|
||||
|
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
using System.Runtime.CompilerServices; |
|
||||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts an 8-bit AV1 block by copying the top neighboring samples into every destination row.
|
|
||||
/// </summary>
|
|
||||
internal readonly struct Av1VerticalPredictor : IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The number of top samples consumed and samples written to each destination row.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockWidth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of destination rows.
|
|
||||
/// </summary>
|
|
||||
private readonly nuint blockHeight; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1VerticalPredictor"/> struct for explicit block dimensions.
|
|
||||
/// </summary>
|
|
||||
/// <param name="blockSize">The predicted block dimensions in samples.</param>
|
|
||||
public Av1VerticalPredictor(Size blockSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)blockSize.Width; |
|
||||
this.blockHeight = (nuint)blockSize.Height; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="Av1VerticalPredictor"/> struct for a transform size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The transform size whose dimensions define the predicted block.</param>
|
|
||||
public Av1VerticalPredictor(Av1TransformSize transformSize) |
|
||||
{ |
|
||||
this.blockWidth = (nuint)transformSize.GetWidth(); |
|
||||
this.blockHeight = (nuint)transformSize.GetHeight(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Predicts a transform block by extending its top edge vertically.
|
|
||||
/// </summary>
|
|
||||
/// <param name="transformSize">The predicted block dimensions.</param>
|
|
||||
/// <param name="destination">The destination block.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The top neighboring samples.</param>
|
|
||||
/// <param name="left">The unused left-neighbor buffer required by the common predictor signature.</param>
|
|
||||
public static void PredictScalar(Av1TransformSize transformSize, Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
=> new Av1VerticalPredictor(transformSize).PredictScalar(destination, stride, above, left); |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
/// <remarks>SVT-AV1: <c>highbd_v_predictor</c>.</remarks>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left) |
|
||||
{ |
|
||||
Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); |
|
||||
Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); |
|
||||
Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); |
|
||||
Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); |
|
||||
ref byte aboveRef = ref above[0]; |
|
||||
ref byte destinationRef = ref destination[0]; |
|
||||
uint width = (uint)this.blockWidth; |
|
||||
for (nuint r = 0; r < this.blockHeight; ++r) |
|
||||
{ |
|
||||
Unsafe.CopyBlock(ref destinationRef, ref aboveRef, width); |
|
||||
destinationRef = ref Unsafe.Add(ref destinationRef, stride); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
// Copyright (c) Six Labors.
|
|
||||
// Licensed under the Six Labors Split License.
|
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Defines scalar reconstruction of an 8-bit AV1 intra-prediction block from its neighboring samples.
|
|
||||
/// </summary>
|
|
||||
internal interface IAv1Predictor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Writes the predicted block using scalar 8-bit arithmetic.
|
|
||||
/// </summary>
|
|
||||
/// <param name="destination">The destination block, starting at its top-left sample.</param>
|
|
||||
/// <param name="stride">The distance, in samples, between destination rows.</param>
|
|
||||
/// <param name="above">The neighboring samples immediately above the block.</param>
|
|
||||
/// <param name="left">The neighboring samples immediately left of the block.</param>
|
|
||||
public void PredictScalar(Span<byte> destination, nuint stride, Span<byte> above, Span<byte> left); |
|
||||
} |
|
||||
File diff suppressed because it is too large
Loading…
Reference in new issue