mirror of https://github.com/SixLabors/ImageSharp
126 changed files with 13889 additions and 7852 deletions
@ -0,0 +1,519 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
|||
|
|||
/// <summary>
|
|||
/// Owns the adaptive AV1 distributions currently implemented by the frame and tile syntax decoders.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// One frame context supplies the initial state copied into every tile context. Each tile adapts an independent working
|
|||
/// copy, and only the tile selected by <c>context_update_tile_id</c> supplies the completed frame snapshot.
|
|||
/// </remarks>
|
|||
internal sealed class Av1FrameEntropyContext |
|||
{ |
|||
/// <summary>
|
|||
/// The inclusive upper bound of the first AV1 coefficient-probability quantizer band.
|
|||
/// </summary>
|
|||
private const int FirstQuantizerBandMaximum = 20; |
|||
|
|||
/// <summary>
|
|||
/// The inclusive upper bound of the second AV1 coefficient-probability quantizer band.
|
|||
/// </summary>
|
|||
private const int SecondQuantizerBandMaximum = 60; |
|||
|
|||
/// <summary>
|
|||
/// The inclusive upper bound of the third AV1 coefficient-probability quantizer band.
|
|||
/// </summary>
|
|||
private const int ThirdQuantizerBandMaximum = 120; |
|||
|
|||
/// <summary>
|
|||
/// The immutable normative contexts used to restore reusable frame state without rebuilding distribution graphs.
|
|||
/// </summary>
|
|||
private static readonly Av1FrameEntropyContext[] DefaultPrototypes = |
|||
[ |
|||
new((byte)0), |
|||
new((byte)1), |
|||
new((byte)2), |
|||
new((byte)3) |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1FrameEntropyContext"/> class from the normative default
|
|||
/// distributions selected by a frame quantizer index.
|
|||
/// </summary>
|
|||
/// <param name="qIndex">The frame base quantizer index selecting coefficient distribution defaults.</param>
|
|||
public Av1FrameEntropyContext(int qIndex) |
|||
: this(DefaultPrototypes[GetQContext(qIndex)]) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1FrameEntropyContext"/> class as an immutable normative prototype.
|
|||
/// </summary>
|
|||
/// <param name="qContext">The zero-based coefficient-probability quantizer band.</param>
|
|||
private Av1FrameEntropyContext(byte qContext) |
|||
{ |
|||
int qIndex = qContext switch |
|||
{ |
|||
0 => 0, |
|||
1 => FirstQuantizerBandMaximum + 1, |
|||
2 => SecondQuantizerBandMaximum + 1, |
|||
_ => ThirdQuantizerBandMaximum + 1 |
|||
}; |
|||
|
|||
// Every default-distribution accessor constructs independently mutable state. Retaining those returned
|
|||
// graphs directly confines generated-table construction to the four process-wide quantizer-band prototypes.
|
|||
this.IntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; |
|||
this.DisplacementVector = new(); |
|||
this.SwitchableRestoration = Av1DefaultDistributions.SwitchableRestoration; |
|||
this.WienerRestoration = Av1DefaultDistributions.WienerRestoration; |
|||
this.SgrProjectionRestoration = Av1DefaultDistributions.SgrProjectionRestoration; |
|||
this.PaletteYMode = Av1DefaultDistributions.PaletteYMode; |
|||
this.PaletteUvMode = Av1DefaultDistributions.PaletteUvMode; |
|||
this.PaletteYSize = Av1DefaultDistributions.PaletteYSize; |
|||
this.PaletteUvSize = Av1DefaultDistributions.PaletteUvSize; |
|||
this.PaletteYColorIndex = Av1DefaultDistributions.PaletteYColorIndex; |
|||
this.PaletteUvColorIndex = Av1DefaultDistributions.PaletteUvColorIndex; |
|||
this.PartitionTypes = Av1DefaultDistributions.PartitionTypes; |
|||
this.KeyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; |
|||
this.UvMode = Av1DefaultDistributions.UvMode; |
|||
this.Skip = Av1DefaultDistributions.Skip; |
|||
this.SkipMode = Av1DefaultDistributions.SkipMode; |
|||
this.DeltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; |
|||
this.DeltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; |
|||
this.SegmentId = Av1DefaultDistributions.SegmentId; |
|||
this.AngleDelta = Av1DefaultDistributions.AngleDelta; |
|||
this.FilterIntraMode = Av1DefaultDistributions.FilterIntraMode; |
|||
this.FilterIntra = Av1DefaultDistributions.FilterIntra; |
|||
this.TransformSize = Av1DefaultDistributions.TransformSize; |
|||
this.ChromaFromLumaSign = Av1DefaultDistributions.ChromaFromLumaSign; |
|||
this.ChromaFromLumaAlpha = Av1DefaultDistributions.ChromaFromLumaAlpha; |
|||
this.IntraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; |
|||
this.InterExtendedTransform = Av1DefaultDistributions.InterExtendedTransform; |
|||
|
|||
// Coefficient defaults use one of four quantizer bands. Their array shapes remain fixed, so later tile resets
|
|||
// copy only thresholds and update counts into this context's already allocated distribution graph.
|
|||
this.EndOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); |
|||
this.CoefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); |
|||
this.BaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); |
|||
this.DcSign = Av1DefaultDistributions.GetDcSign(qIndex); |
|||
this.CoefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex); |
|||
this.TransformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex); |
|||
this.EndOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1FrameEntropyContext"/> class with an independently adaptable copy of a prototype.
|
|||
/// </summary>
|
|||
/// <param name="source">The prototype or retained context whose state is copied.</param>
|
|||
private Av1FrameEntropyContext(Av1FrameEntropyContext source) |
|||
{ |
|||
// Session and retained-frame contexts need one mutable graph, not four generated quantizer-band graphs whose
|
|||
// unused bands are immediately discarded. Deep-copy the already selected prototype shape exactly once.
|
|||
this.IntraBlockCopy = source.IntraBlockCopy.CreateCopy(); |
|||
this.DisplacementVector = new(); |
|||
this.DisplacementVector.CopyFrom(source.DisplacementVector); |
|||
this.SwitchableRestoration = source.SwitchableRestoration.CreateCopy(); |
|||
this.WienerRestoration = source.WienerRestoration.CreateCopy(); |
|||
this.SgrProjectionRestoration = source.SgrProjectionRestoration.CreateCopy(); |
|||
this.PaletteYMode = Av1Distribution.CreateCopy(source.PaletteYMode); |
|||
this.PaletteUvMode = Av1Distribution.CreateCopy(source.PaletteUvMode); |
|||
this.PaletteYSize = Av1Distribution.CreateCopy(source.PaletteYSize); |
|||
this.PaletteUvSize = Av1Distribution.CreateCopy(source.PaletteUvSize); |
|||
this.PaletteYColorIndex = Av1Distribution.CreateCopy(source.PaletteYColorIndex); |
|||
this.PaletteUvColorIndex = Av1Distribution.CreateCopy(source.PaletteUvColorIndex); |
|||
this.PartitionTypes = Av1Distribution.CreateCopy(source.PartitionTypes); |
|||
this.KeyFrameYMode = Av1Distribution.CreateCopy(source.KeyFrameYMode); |
|||
this.UvMode = Av1Distribution.CreateCopy(source.UvMode); |
|||
this.Skip = Av1Distribution.CreateCopy(source.Skip); |
|||
this.SkipMode = Av1Distribution.CreateCopy(source.SkipMode); |
|||
this.DeltaLoopFilterAbsolute = source.DeltaLoopFilterAbsolute.CreateCopy(); |
|||
this.DeltaQuantizerAbsolute = source.DeltaQuantizerAbsolute.CreateCopy(); |
|||
this.SegmentId = Av1Distribution.CreateCopy(source.SegmentId); |
|||
this.AngleDelta = Av1Distribution.CreateCopy(source.AngleDelta); |
|||
this.FilterIntraMode = source.FilterIntraMode.CreateCopy(); |
|||
this.FilterIntra = Av1Distribution.CreateCopy(source.FilterIntra); |
|||
this.TransformSize = Av1Distribution.CreateCopy(source.TransformSize); |
|||
this.EndOfBlockFlag = Av1Distribution.CreateCopy(source.EndOfBlockFlag); |
|||
this.CoefficientsBase = Av1Distribution.CreateCopy(source.CoefficientsBase); |
|||
this.BaseEndOfBlock = Av1Distribution.CreateCopy(source.BaseEndOfBlock); |
|||
this.DcSign = Av1Distribution.CreateCopy(source.DcSign); |
|||
this.CoefficientsBaseRange = Av1Distribution.CreateCopy(source.CoefficientsBaseRange); |
|||
this.TransformBlockSkip = Av1Distribution.CreateCopy(source.TransformBlockSkip); |
|||
this.EndOfBlockExtra = Av1Distribution.CreateCopy(source.EndOfBlockExtra); |
|||
this.ChromaFromLumaSign = source.ChromaFromLumaSign.CreateCopy(); |
|||
this.ChromaFromLumaAlpha = Av1Distribution.CreateCopy(source.ChromaFromLumaAlpha); |
|||
this.IntraExtendedTransform = Av1Distribution.CreateCopy(source.IntraExtendedTransform); |
|||
this.InterExtendedTransform = Av1Distribution.CreateCopy(source.InterExtendedTransform); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the intra-block-copy distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution IntraBlockCopy { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the integer displacement-vector context used by intra-block copy.
|
|||
/// </summary>
|
|||
public Av1DisplacementVectorContext DisplacementVector { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the switchable loop-restoration distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution SwitchableRestoration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the Wiener loop-restoration distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution WienerRestoration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the self-guided loop-restoration distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution SgrProjectionRestoration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma palette-mode distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] PaletteYMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma palette-mode distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] PaletteUvMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma palette-size distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] PaletteYSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma palette-size distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] PaletteUvSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma palette color-index distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] PaletteYColorIndex { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma palette color-index distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] PaletteUvColorIndex { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the partition-type distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] PartitionTypes { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the key-frame luma-mode distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] KeyFrameYMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma intra-mode distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] UvMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-skip distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] Skip { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the skip-mode distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] SkipMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the absolute loop-filter delta distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution DeltaLoopFilterAbsolute { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the absolute quantizer delta distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution DeltaQuantizerAbsolute { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the spatial segment-identifier distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] SegmentId { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the directional angle-delta distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] AngleDelta { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter-intra mode distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution FilterIntraMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the filter-intra enable distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] FilterIntra { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-size distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] TransformSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the end-of-block token distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] EndOfBlockFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coefficient base-level distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] CoefficientsBase { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the final-nonzero coefficient distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] BaseEndOfBlock { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the DC sign distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] DcSign { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the coefficient base-range distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] CoefficientsBaseRange { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transform-block skip distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] TransformBlockSkip { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the end-of-block extra-bit distributions selected for the frame base quantizer.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] EndOfBlockExtra { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the joint chroma-from-luma sign distribution.
|
|||
/// </summary>
|
|||
public Av1Distribution ChromaFromLumaSign { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the chroma-from-luma alpha-magnitude distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[] ChromaFromLumaAlpha { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the intra transform-type distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][][] IntraExtendedTransform { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the inter transform-type distributions.
|
|||
/// </summary>
|
|||
public Av1Distribution[][] InterExtendedTransform { get; } |
|||
|
|||
/// <summary>
|
|||
/// Restores the normative frame defaults selected by a base quantizer index.
|
|||
/// </summary>
|
|||
/// <param name="qIndex">The frame base quantizer index selecting coefficient distribution defaults.</param>
|
|||
public void ResetToDefaults(int qIndex) |
|||
{ |
|||
int qContext = GetQContext(qIndex); |
|||
|
|||
// The prototypes are never exposed to a range reader. Copying their state lets a decoder session reuse the
|
|||
// same three mutable object graphs even when successive frames select different coefficient-model bands.
|
|||
this.CopyFrom(DefaultPrototypes[qContext]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps a frame base quantizer to its normative coefficient-probability initialization band.
|
|||
/// </summary>
|
|||
/// <param name="qIndex">The frame base quantizer index.</param>
|
|||
/// <returns>The zero-based quantizer-band index.</returns>
|
|||
private static int GetQContext(int qIndex) |
|||
=> qIndex switch |
|||
{ |
|||
<= FirstQuantizerBandMaximum => 0, |
|||
<= SecondQuantizerBandMaximum => 1, |
|||
<= ThirdQuantizerBandMaximum => 2, |
|||
_ => 3 |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Replaces every probability threshold and adaptation count with state copied from another frame context.
|
|||
/// </summary>
|
|||
/// <param name="source">The frame context state to copy.</param>
|
|||
public void CopyFrom(Av1FrameEntropyContext source) |
|||
{ |
|||
this.IntraBlockCopy.CopyFrom(source.IntraBlockCopy); |
|||
this.DisplacementVector.CopyFrom(source.DisplacementVector); |
|||
this.SwitchableRestoration.CopyFrom(source.SwitchableRestoration); |
|||
this.WienerRestoration.CopyFrom(source.WienerRestoration); |
|||
this.SgrProjectionRestoration.CopyFrom(source.SgrProjectionRestoration); |
|||
CopyState(source.PaletteYMode, this.PaletteYMode); |
|||
CopyState(source.PaletteUvMode, this.PaletteUvMode); |
|||
CopyState(source.PaletteYSize, this.PaletteYSize); |
|||
CopyState(source.PaletteUvSize, this.PaletteUvSize); |
|||
CopyState(source.PaletteYColorIndex, this.PaletteYColorIndex); |
|||
CopyState(source.PaletteUvColorIndex, this.PaletteUvColorIndex); |
|||
CopyState(source.PartitionTypes, this.PartitionTypes); |
|||
CopyState(source.KeyFrameYMode, this.KeyFrameYMode); |
|||
CopyState(source.UvMode, this.UvMode); |
|||
CopyState(source.Skip, this.Skip); |
|||
CopyState(source.SkipMode, this.SkipMode); |
|||
this.DeltaLoopFilterAbsolute.CopyFrom(source.DeltaLoopFilterAbsolute); |
|||
this.DeltaQuantizerAbsolute.CopyFrom(source.DeltaQuantizerAbsolute); |
|||
CopyState(source.SegmentId, this.SegmentId); |
|||
CopyState(source.AngleDelta, this.AngleDelta); |
|||
this.FilterIntraMode.CopyFrom(source.FilterIntraMode); |
|||
CopyState(source.FilterIntra, this.FilterIntra); |
|||
CopyState(source.TransformSize, this.TransformSize); |
|||
CopyState(source.EndOfBlockFlag, this.EndOfBlockFlag); |
|||
CopyState(source.CoefficientsBase, this.CoefficientsBase); |
|||
CopyState(source.BaseEndOfBlock, this.BaseEndOfBlock); |
|||
CopyState(source.DcSign, this.DcSign); |
|||
CopyState(source.CoefficientsBaseRange, this.CoefficientsBaseRange); |
|||
CopyState(source.TransformBlockSkip, this.TransformBlockSkip); |
|||
CopyState(source.EndOfBlockExtra, this.EndOfBlockExtra); |
|||
this.ChromaFromLumaSign.CopyFrom(source.ChromaFromLumaSign); |
|||
CopyState(source.ChromaFromLumaAlpha, this.ChromaFromLumaAlpha); |
|||
CopyState(source.IntraExtendedTransform, this.IntraExtendedTransform); |
|||
CopyState(source.InterExtendedTransform, this.InterExtendedTransform); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies this tile-adapted context into a destination used as completed frame state.
|
|||
/// </summary>
|
|||
/// <param name="destination">The independently owned frame context that receives the snapshot.</param>
|
|||
/// <remarks>
|
|||
/// AV1 resets CDF observation counters after publishing the context-update tile. The copied thresholds remain
|
|||
/// adapted, while the next frame starts its update-rate history from zero.
|
|||
/// </remarks>
|
|||
public void SnapshotTo(Av1FrameEntropyContext destination) |
|||
{ |
|||
destination.CopyFrom(this); |
|||
destination.ResetUpdateCounts(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resets the observation count of every distribution without changing its probability thresholds.
|
|||
/// </summary>
|
|||
private void ResetUpdateCounts() |
|||
{ |
|||
this.IntraBlockCopy.ResetUpdateCount(); |
|||
this.DisplacementVector.ResetUpdateCounts(); |
|||
this.SwitchableRestoration.ResetUpdateCount(); |
|||
this.WienerRestoration.ResetUpdateCount(); |
|||
this.SgrProjectionRestoration.ResetUpdateCount(); |
|||
ResetUpdateCounts(this.PaletteYMode); |
|||
ResetUpdateCounts(this.PaletteUvMode); |
|||
ResetUpdateCounts(this.PaletteYSize); |
|||
ResetUpdateCounts(this.PaletteUvSize); |
|||
ResetUpdateCounts(this.PaletteYColorIndex); |
|||
ResetUpdateCounts(this.PaletteUvColorIndex); |
|||
ResetUpdateCounts(this.PartitionTypes); |
|||
ResetUpdateCounts(this.KeyFrameYMode); |
|||
ResetUpdateCounts(this.UvMode); |
|||
ResetUpdateCounts(this.Skip); |
|||
ResetUpdateCounts(this.SkipMode); |
|||
this.DeltaLoopFilterAbsolute.ResetUpdateCount(); |
|||
this.DeltaQuantizerAbsolute.ResetUpdateCount(); |
|||
ResetUpdateCounts(this.SegmentId); |
|||
ResetUpdateCounts(this.AngleDelta); |
|||
this.FilterIntraMode.ResetUpdateCount(); |
|||
ResetUpdateCounts(this.FilterIntra); |
|||
ResetUpdateCounts(this.TransformSize); |
|||
ResetUpdateCounts(this.EndOfBlockFlag); |
|||
ResetUpdateCounts(this.CoefficientsBase); |
|||
ResetUpdateCounts(this.BaseEndOfBlock); |
|||
ResetUpdateCounts(this.DcSign); |
|||
ResetUpdateCounts(this.CoefficientsBaseRange); |
|||
ResetUpdateCounts(this.TransformBlockSkip); |
|||
ResetUpdateCounts(this.EndOfBlockExtra); |
|||
this.ChromaFromLumaSign.ResetUpdateCount(); |
|||
ResetUpdateCounts(this.ChromaFromLumaAlpha); |
|||
ResetUpdateCounts(this.IntraExtendedTransform); |
|||
ResetUpdateCounts(this.InterExtendedTransform); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies one distribution row into an existing row with the same default-table shape.
|
|||
/// </summary>
|
|||
/// <param name="source">The source distribution row.</param>
|
|||
/// <param name="destination">The destination distribution row.</param>
|
|||
private static void CopyState(Av1Distribution[] source, Av1Distribution[] destination) |
|||
{ |
|||
for (int index = 0; index < source.Length; index++) |
|||
{ |
|||
destination[index].CopyFrom(source[index]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies a two-dimensional distribution table into an existing table with the same default-table shape.
|
|||
/// </summary>
|
|||
/// <param name="source">The source distribution table.</param>
|
|||
/// <param name="destination">The destination distribution table.</param>
|
|||
private static void CopyState(Av1Distribution[][] source, Av1Distribution[][] destination) |
|||
{ |
|||
for (int index = 0; index < source.Length; index++) |
|||
{ |
|||
CopyState(source[index], destination[index]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Copies a three-dimensional distribution table into an existing table with the same default-table shape.
|
|||
/// </summary>
|
|||
/// <param name="source">The source distribution table.</param>
|
|||
/// <param name="destination">The destination distribution table.</param>
|
|||
private static void CopyState(Av1Distribution[][][] source, Av1Distribution[][][] destination) |
|||
{ |
|||
for (int index = 0; index < source.Length; index++) |
|||
{ |
|||
CopyState(source[index], destination[index]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resets observation counts in one distribution row.
|
|||
/// </summary>
|
|||
/// <param name="distributions">The distribution row to reset.</param>
|
|||
private static void ResetUpdateCounts(Av1Distribution[] distributions) |
|||
{ |
|||
for (int index = 0; index < distributions.Length; index++) |
|||
{ |
|||
distributions[index].ResetUpdateCount(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resets observation counts in a two-dimensional distribution table.
|
|||
/// </summary>
|
|||
/// <param name="distributions">The distribution table to reset.</param>
|
|||
private static void ResetUpdateCounts(Av1Distribution[][] distributions) |
|||
{ |
|||
for (int index = 0; index < distributions.Length; index++) |
|||
{ |
|||
ResetUpdateCounts(distributions[index]); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resets observation counts in a three-dimensional distribution table.
|
|||
/// </summary>
|
|||
/// <param name="distributions">The distribution table to reset.</param>
|
|||
private static void ResetUpdateCounts(Av1Distribution[][][] distributions) |
|||
{ |
|||
for (int index = 0; index < distributions.Length; index++) |
|||
{ |
|||
ResetUpdateCounts(distributions[index]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,150 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
|||
|
|||
/// <summary>
|
|||
/// Owns the reusable frame-base, tile-working, and published AV1 entropy contexts for one decoder session.
|
|||
/// </summary>
|
|||
internal sealed class Av1FrameEntropyContexts |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum number of live reference-map and presentation owners plus the newly reconstructed frame awaiting
|
|||
/// commit.
|
|||
/// </summary>
|
|||
private const int MaximumSnapshotCount = Av1Constants.ReferenceFrameCount + 2; |
|||
|
|||
/// <summary>
|
|||
/// Session-local returned snapshot graphs available for later refreshed frames.
|
|||
/// </summary>
|
|||
private InlineArray10<Av1FrameEntropyContext?> returnedSnapshots; |
|||
|
|||
/// <summary>
|
|||
/// The number of returned snapshot graphs currently available for reuse.
|
|||
/// </summary>
|
|||
private int returnedSnapshotCount; |
|||
|
|||
/// <summary>
|
|||
/// The base quantizer index used to initialize a newly required snapshot graph.
|
|||
/// </summary>
|
|||
private int currentQIndex; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1FrameEntropyContexts"/> class.
|
|||
/// </summary>
|
|||
/// <param name="qIndex">The initial frame base quantizer index.</param>
|
|||
public Av1FrameEntropyContexts(int qIndex) |
|||
{ |
|||
this.Base = new(qIndex); |
|||
this.Working = new(qIndex); |
|||
this.Published = new(qIndex); |
|||
this.currentQIndex = qIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the unchanged frame context from which each independently decoded tile starts.
|
|||
/// </summary>
|
|||
public Av1FrameEntropyContext Base { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the tile-local context reused sequentially for each tile in the current frame.
|
|||
/// </summary>
|
|||
public Av1FrameEntropyContext Working { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the completed frame context selected by the signaled context-update tile, or the unchanged frame-base
|
|||
/// context when frame-end updates are disabled.
|
|||
/// </summary>
|
|||
public Av1FrameEntropyContext Published { get; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes frame entropy state from either a retained primary reference or normative quantizer-band defaults.
|
|||
/// </summary>
|
|||
/// <param name="qIndex">The frame base quantizer index selecting coefficient distribution defaults.</param>
|
|||
/// <param name="primaryReferenceContext">
|
|||
/// The retained primary-reference context, or <see langword="null"/> when the frame selects normative defaults.
|
|||
/// </param>
|
|||
public void BeginFrame(int qIndex, Av1FrameEntropyContext? primaryReferenceContext) |
|||
{ |
|||
this.currentQIndex = qIndex; |
|||
if (primaryReferenceContext is null) |
|||
{ |
|||
this.Base.ResetToDefaults(qIndex); |
|||
} |
|||
else |
|||
{ |
|||
// A retained context is independent from the working and published graphs. Copying it here preserves the
|
|||
// reference owner's snapshot while the current frame adapts its own tile-local state.
|
|||
this.Base.CopyFrom(primaryReferenceContext); |
|||
} |
|||
|
|||
// The context-update tile can precede later tiles. Published therefore cannot alias Working: a later tile
|
|||
// must be free to overwrite Working while the selected completed-frame state remains available to the owner.
|
|||
this.Base.SnapshotTo(this.Published); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears active frame entropy state when a new coded sequence invalidates the complete reference map.
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
this.currentQIndex = 0; |
|||
this.Base.ResetToDefaults(this.currentQIndex); |
|||
this.Base.SnapshotTo(this.Working); |
|||
this.Base.SnapshotTo(this.Published); |
|||
|
|||
// Returned graphs contain no live reference state and remain private to this decoder. Retaining them here
|
|||
// allows the next sequence to reuse peak reference ownership without a static cross-decode pool.
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rents an independently owned, reset-counter snapshot of the completed frame entropy context.
|
|||
/// </summary>
|
|||
/// <returns>The snapshot that must later be returned through <see cref="ReturnSnapshot"/>.</returns>
|
|||
public Av1FrameEntropyContext RentPublishedSnapshot() |
|||
{ |
|||
Av1FrameEntropyContext snapshot; |
|||
if (this.returnedSnapshotCount == 0) |
|||
{ |
|||
// Eight slots can own distinct frames while the selected output owns a ninth frame no longer present in
|
|||
// the map. Rent one further graph before commit releases the owner displaced by the completed frame.
|
|||
snapshot = new(this.currentQIndex); |
|||
} |
|||
else |
|||
{ |
|||
int snapshotIndex = --this.returnedSnapshotCount; |
|||
snapshot = this.returnedSnapshots[snapshotIndex]!; |
|||
this.returnedSnapshots[snapshotIndex] = null; |
|||
} |
|||
|
|||
this.Published.SnapshotTo(snapshot); |
|||
return snapshot; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a retained-frame entropy snapshot to this decoder session for later reuse.
|
|||
/// </summary>
|
|||
/// <param name="snapshot">The snapshot whose reference-frame ownership has ended.</param>
|
|||
public void ReturnSnapshot(Av1FrameEntropyContext snapshot) |
|||
{ |
|||
// The fixed capacity covers eight distinct slot owners, one detached presentation owner, and the replacement
|
|||
// frame rented before commit. Av1ReferenceFrame returns each graph exactly once, so the session cannot exceed
|
|||
// this bound.
|
|||
this.returnedSnapshots[this.returnedSnapshotCount++] = snapshot; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides inline storage for every entropy snapshot graph that one decoder session can allocate concurrently.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The stored reference type.</typeparam>
|
|||
[InlineArray(MaximumSnapshotCount)] |
|||
private struct InlineArray10<T> |
|||
{ |
|||
/// <summary>
|
|||
/// The first element in the compiler-expanded inline buffer.
|
|||
/// </summary>
|
|||
private T element; |
|||
} |
|||
} |
|||
@ -0,0 +1,234 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
|
|||
/// <summary>
|
|||
/// Stores one AV1 global-motion model in the codec's fixed-point affine matrix domain.
|
|||
/// </summary>
|
|||
internal struct Av1GlobalMotionParameters |
|||
{ |
|||
/// <summary>
|
|||
/// The number of fractional bits carried by every stored matrix parameter.
|
|||
/// </summary>
|
|||
public const int ModelPrecisionBits = 16; |
|||
|
|||
/// <summary>
|
|||
/// The fixed-point representation of one in the global-motion matrix domain.
|
|||
/// </summary>
|
|||
public const int ModelScale = 1 << ModelPrecisionBits; |
|||
|
|||
/// <summary>
|
|||
/// The number of low-order bits removed from the derived shear parameters.
|
|||
/// </summary>
|
|||
private const int ShearParameterReductionBits = 6; |
|||
|
|||
/// <summary>
|
|||
/// The number of fractional bits carried by entries in <see cref="ReciprocalTable"/>.
|
|||
/// </summary>
|
|||
private const int ReciprocalPrecisionBits = 14; |
|||
|
|||
/// <summary>
|
|||
/// The number of divisor-fraction bits used to index <see cref="ReciprocalTable"/>.
|
|||
/// </summary>
|
|||
private const int ReciprocalIndexBits = 8; |
|||
|
|||
/// <summary>
|
|||
/// The six parameters ordered as horizontal translation, vertical translation, and the four affine coefficients.
|
|||
/// </summary>
|
|||
private InlineArray6<int> matrix; |
|||
|
|||
/// <summary>
|
|||
/// Gets an identity global-motion model.
|
|||
/// </summary>
|
|||
public static Av1GlobalMotionParameters Identity |
|||
{ |
|||
get |
|||
{ |
|||
Av1GlobalMotionParameters result = default; |
|||
result.matrix[2] = ModelScale; |
|||
result.matrix[5] = ModelScale; |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the geometric model represented by the matrix parameters.
|
|||
/// </summary>
|
|||
public Av1GlobalMotionType Type { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reduced horizontal scale delta used by warped prediction.
|
|||
/// </summary>
|
|||
public short Alpha { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reduced horizontal shear used by warped prediction.
|
|||
/// </summary>
|
|||
public short Beta { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reduced vertical shear used by warped prediction.
|
|||
/// </summary>
|
|||
public short Gamma { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the reduced vertical scale delta used by warped prediction.
|
|||
/// </summary>
|
|||
public short Delta { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the affine model violates AV1's permitted shear bounds.
|
|||
/// </summary>
|
|||
public bool IsInvalid { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the fixed-point reciprocal lookup used by AV1's affine shear derivation.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<ushort> ReciprocalTable => |
|||
[ |
|||
16384, 16320, 16257, 16194, 16132, 16070, 16009, 15948, 15888, 15828, 15768, |
|||
15709, 15650, 15592, 15534, 15477, 15420, 15364, 15308, 15252, 15197, 15142, |
|||
15087, 15033, 14980, 14926, 14873, 14821, 14769, 14717, 14665, 14614, 14564, |
|||
14513, 14463, 14413, 14364, 14315, 14266, 14218, 14170, 14122, 14075, 14028, |
|||
13981, 13935, 13888, 13843, 13797, 13752, 13707, 13662, 13618, 13574, 13530, |
|||
13487, 13443, 13400, 13358, 13315, 13273, 13231, 13190, 13148, 13107, 13066, |
|||
13026, 12985, 12945, 12906, 12866, 12827, 12788, 12749, 12710, 12672, 12633, |
|||
12596, 12558, 12520, 12483, 12446, 12409, 12373, 12336, 12300, 12264, 12228, |
|||
12193, 12157, 12122, 12087, 12053, 12018, 11984, 11950, 11916, 11882, 11848, |
|||
11815, 11782, 11749, 11716, 11683, 11651, 11619, 11586, 11555, 11523, 11491, |
|||
11460, 11429, 11398, 11367, 11336, 11305, 11275, 11245, 11215, 11185, 11155, |
|||
11125, 11096, 11067, 11038, 11009, 10980, 10951, 10923, 10894, 10866, 10838, |
|||
10810, 10782, 10755, 10727, 10700, 10673, 10645, 10618, 10592, 10565, 10538, |
|||
10512, 10486, 10460, 10434, 10408, 10382, 10356, 10331, 10305, 10280, 10255, |
|||
10230, 10205, 10180, 10156, 10131, 10107, 10082, 10058, 10034, 10010, 9986, |
|||
9963, 9939, 9916, 9892, 9869, 9846, 9823, 9800, 9777, 9754, 9732, 9709, 9687, |
|||
9664, 9642, 9620, 9598, 9576, 9554, 9533, 9511, 9489, 9468, 9447, 9425, 9404, |
|||
9383, 9362, 9341, 9321, 9300, 9279, 9259, 9239, 9218, 9198, 9178, 9158, 9138, |
|||
9118, 9098, 9079, 9059, 9039, 9020, 9001, 8981, 8962, 8943, 8924, 8905, 8886, |
|||
8867, 8849, 8830, 8812, 8793, 8775, 8756, 8738, 8720, 8702, 8684, 8666, 8648, |
|||
8630, 8613, 8595, 8577, 8560, 8542, 8525, 8508, 8490, 8473, 8456, 8439, 8422, |
|||
8405, 8389, 8372, 8355, 8339, 8322, 8306, 8289, 8273, 8257, 8240, 8224, 8208, |
|||
8192, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a matrix parameter in AV1 affine-transform order.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based matrix parameter index.</param>
|
|||
/// <returns>The fixed-point matrix parameter.</returns>
|
|||
public int this[int index] |
|||
{ |
|||
get => this.matrix[index]; |
|||
set => this.matrix[index] = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Derives the reduced shear parameters and records whether the complete affine model is valid.
|
|||
/// </summary>
|
|||
public void UpdateShearParameters() |
|||
{ |
|||
Span<int> values = this.matrix; |
|||
this.Alpha = 0; |
|||
this.Beta = 0; |
|||
this.Gamma = 0; |
|||
this.Delta = 0; |
|||
|
|||
if (values[2] <= 0) |
|||
{ |
|||
this.IsInvalid = true; |
|||
return; |
|||
} |
|||
|
|||
this.Alpha = (short)Math.Clamp(values[2] - ModelScale, short.MinValue, short.MaxValue); |
|||
this.Beta = (short)Math.Clamp(values[3], short.MinValue, short.MaxValue); |
|||
|
|||
// AV1 derives gamma and delta by multiplying with a fixed-precision reciprocal of the horizontal scale.
|
|||
// The reciprocal lookup is normative; integer division would produce different warped sample positions.
|
|||
int reciprocal = ResolveDivisor((uint)values[2], out int reciprocalShift); |
|||
long scaledVerticalCoefficient = (long)values[4] * ModelScale * reciprocal; |
|||
this.Gamma = (short)Math.Clamp(RoundPowerOf2Signed(scaledVerticalCoefficient, reciprocalShift), short.MinValue, short.MaxValue); |
|||
|
|||
long scaledCrossCoefficient = (long)values[3] * values[4] * reciprocal; |
|||
long verticalScaleDelta = values[5] - RoundPowerOf2Signed(scaledCrossCoefficient, reciprocalShift) - ModelScale; |
|||
this.Delta = (short)Math.Clamp(verticalScaleDelta, short.MinValue, short.MaxValue); |
|||
|
|||
// Warped filtering addresses a coarser parameter grid than the stored affine matrix. Symmetric rounding is
|
|||
// required here so negative shear values are quantized identically to their positive counterparts.
|
|||
this.Alpha = ReduceShearParameter(this.Alpha); |
|||
this.Beta = ReduceShearParameter(this.Beta); |
|||
this.Gamma = ReduceShearParameter(this.Gamma); |
|||
this.Delta = ReduceShearParameter(this.Delta); |
|||
|
|||
// These weighted L1 bounds are the AV1 validity test for the two shear axes. Equality is invalid because the
|
|||
// warped-filter footprint would no longer remain inside the permitted affine sampling envelope.
|
|||
this.IsInvalid = |
|||
((4 * Math.Abs((int)this.Alpha)) + (7 * Math.Abs((int)this.Beta)) >= ModelScale) || |
|||
((4 * Math.Abs((int)this.Gamma)) + (4 * Math.Abs((int)this.Delta)) >= ModelScale); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Quantizes one signed shear parameter to AV1's warped-filter precision.
|
|||
/// </summary>
|
|||
/// <param name="value">The full-precision shear parameter.</param>
|
|||
/// <returns>The reduced shear parameter.</returns>
|
|||
private static short ReduceShearParameter(short value) |
|||
=> (short)(RoundPowerOf2Signed(value, ShearParameterReductionBits) * (1 << ShearParameterReductionBits)); |
|||
|
|||
/// <summary>
|
|||
/// Resolves a positive divisor into AV1's fixed-point reciprocal representation.
|
|||
/// </summary>
|
|||
/// <param name="divisor">The positive divisor.</param>
|
|||
/// <param name="shift">Receives the reciprocal's binary scale.</param>
|
|||
/// <returns>The fixed-point reciprocal multiplier.</returns>
|
|||
private static int ResolveDivisor(uint divisor, out int shift) |
|||
{ |
|||
// Normalize the divisor around its highest set bit, then quantize the remaining fraction to the normative
|
|||
// eight-bit table index. Adding the table's fourteen fractional bits yields the scale used by the caller's
|
|||
// rounded multiply instead of a platform-dependent integer division.
|
|||
shift = BitOperations.Log2(divisor); |
|||
int remainder = (int)(divisor - (1U << shift)); |
|||
int reciprocalIndex = shift > ReciprocalIndexBits |
|||
? RoundPowerOf2(remainder, shift - ReciprocalIndexBits) |
|||
: remainder << (ReciprocalIndexBits - shift); |
|||
|
|||
shift += ReciprocalPrecisionBits; |
|||
return ReciprocalTable[reciprocalIndex]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Divides a nonnegative integer by a power of two with nearest-integer rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The nonnegative value.</param>
|
|||
/// <param name="bitCount">The base-two divisor exponent.</param>
|
|||
/// <returns>The rounded quotient.</returns>
|
|||
private static int RoundPowerOf2(int value, int bitCount) |
|||
=> (value + ((1 << bitCount) >> 1)) >> bitCount; |
|||
|
|||
/// <summary>
|
|||
/// Divides a signed integer by a power of two with symmetric nearest-integer rounding.
|
|||
/// </summary>
|
|||
/// <param name="value">The signed value.</param>
|
|||
/// <param name="bitCount">The base-two divisor exponent.</param>
|
|||
/// <returns>The rounded quotient.</returns>
|
|||
private static long RoundPowerOf2Signed(long value, int bitCount) |
|||
=> value < 0 |
|||
? -(((-value) + ((1L << bitCount) >> 1)) >> bitCount) |
|||
: (value + ((1L << bitCount) >> 1)) >> bitCount; |
|||
|
|||
/// <summary>
|
|||
/// Provides inline storage for the six parameters in an AV1 affine matrix.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The stored parameter type.</typeparam>
|
|||
[InlineArray(6)] |
|||
private struct InlineArray6<T> |
|||
{ |
|||
/// <summary>
|
|||
/// The first element in the compiler-expanded inline buffer.
|
|||
/// </summary>
|
|||
private T element; |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the geometric model carried by AV1 global-motion parameters.
|
|||
/// </summary>
|
|||
internal enum Av1GlobalMotionType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// No geometric displacement is applied.
|
|||
/// </summary>
|
|||
Identity = 0, |
|||
|
|||
/// <summary>
|
|||
/// Horizontal and vertical translation are applied.
|
|||
/// </summary>
|
|||
Translation = 1, |
|||
|
|||
/// <summary>
|
|||
/// Translation, rotation, and uniform zoom are applied.
|
|||
/// </summary>
|
|||
RotationZoom = 2, |
|||
|
|||
/// <summary>
|
|||
/// A general six-parameter affine transformation is applied.
|
|||
/// </summary>
|
|||
Affine = 3 |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
|
|||
/// <summary>
|
|||
/// Stores the uncompressed-header reference state retained by one AV1 OBU reader session.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This state describes the eight reference-map slots but does not own reconstructed sample buffers. Pixel ownership
|
|||
/// remains with the decoder's reference-frame store and is committed before this syntax state is completed. CDF,
|
|||
/// segmentation, loop-filter, motion, and layer metadata remain on that retained frame owner; the current header's
|
|||
/// resolved primary-reference slot selects the shared owner instead of duplicating those values here.
|
|||
/// </remarks>
|
|||
internal struct ObuFrameReferenceState |
|||
{ |
|||
/// <summary>
|
|||
/// Stores whether each of the eight reference-map slots can be selected by a later frame.
|
|||
/// </summary>
|
|||
private InlineArray8<bool> referenceValidity; |
|||
|
|||
/// <summary>
|
|||
/// Stores the frame identifier associated with each of the eight reference-map slots.
|
|||
/// </summary>
|
|||
private InlineArray8<uint> referenceFrameIds; |
|||
|
|||
/// <summary>
|
|||
/// Stores the order hint associated with each of the eight reference-map slots.
|
|||
/// </summary>
|
|||
private InlineArray8<uint> referenceOrderHints; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether a completed frame identifier is available for the next header.
|
|||
/// </summary>
|
|||
public bool HasCurrentFrameId { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the frame identifier of the most recently completed frame.
|
|||
/// </summary>
|
|||
public uint CurrentFrameId { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Copies the completed reference-map state into a newly created frame header.
|
|||
/// </summary>
|
|||
/// <param name="frameHeader">The frame header that will parse and derive state from the retained map.</param>
|
|||
public void InitializeFrameHeader(ObuFrameHeader frameHeader) |
|||
{ |
|||
ReadOnlySpan<bool> referenceValidity = this.referenceValidity; |
|||
ReadOnlySpan<uint> referenceFrameIds = this.referenceFrameIds; |
|||
ReadOnlySpan<uint> referenceOrderHints = this.referenceOrderHints; |
|||
|
|||
// Only the eight retained-slot tables cross a frame boundary. The seven inter-reference roles are signaled or
|
|||
// derived afresh for each frame, and the primary context source is resolved from that per-frame mapping.
|
|||
referenceValidity.CopyTo(frameHeader.GetReferenceValidity()); |
|||
referenceFrameIds.CopyTo(frameHeader.GetReferenceFrameIds()); |
|||
referenceOrderHints.CopyTo(frameHeader.GetReferenceOrderHints()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Publishes the reference-map transition produced by a successfully completed frame.
|
|||
/// </summary>
|
|||
/// <param name="frameHeader">The completed frame header whose refresh mask selects the replaced slots.</param>
|
|||
/// <param name="frameIdNumbersPresent">
|
|||
/// A value indicating whether the sequence carries modulo frame identifiers.
|
|||
/// </param>
|
|||
public void CompleteFrame(ObuFrameHeader frameHeader, bool frameIdNumbersPresent) |
|||
{ |
|||
Span<bool> referenceValidity = frameHeader.GetReferenceValidity(); |
|||
Span<uint> referenceFrameIds = frameHeader.GetReferenceFrameIds(); |
|||
Span<uint> referenceOrderHints = frameHeader.GetReferenceOrderHints(); |
|||
|
|||
// Refresh is published only at this successful completion boundary. Updating the completed header first keeps
|
|||
// the same object retained by the reconstructed frame owner synchronized with the next parser-session snapshot.
|
|||
for (int slot = 0; slot < Av1Constants.ReferenceFrameCount; slot++) |
|||
{ |
|||
if ((frameHeader.RefreshFrameFlags & (1U << slot)) != 0) |
|||
{ |
|||
referenceValidity[slot] = true; |
|||
referenceFrameIds[slot] = frameHeader.CurrentFrameId; |
|||
referenceOrderHints[slot] = frameHeader.OrderHint; |
|||
} |
|||
} |
|||
|
|||
referenceValidity.CopyTo(this.referenceValidity); |
|||
referenceFrameIds.CopyTo(this.referenceFrameIds); |
|||
referenceOrderHints.CopyTo(this.referenceOrderHints); |
|||
|
|||
if (frameIdNumbersPresent) |
|||
{ |
|||
// libaom keeps one current_frame_id in decoder-session state. The following header snapshots this value as
|
|||
// its previous identifier before consuming its own current_frame_id syntax.
|
|||
this.CurrentFrameId = frameHeader.CurrentFrameId; |
|||
this.HasCurrentFrameId = true; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears the completed frame identifier and every retained reference-map slot.
|
|||
/// </summary>
|
|||
public void Reset() => this = default; |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,90 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the intra prediction mode used by the chroma planes of an AV1 coding block.
|
|||
/// </summary>
|
|||
internal enum Av1ChromaPredictionMode : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Predicts each sample from the average of the available top and left neighbors.
|
|||
/// </summary>
|
|||
DC, |
|||
|
|||
/// <summary>
|
|||
/// Repeats the top neighboring row vertically through the block.
|
|||
/// </summary>
|
|||
Vertical, |
|||
|
|||
/// <summary>
|
|||
/// Repeats the left neighboring column horizontally through the block.
|
|||
/// </summary>
|
|||
Horizontal, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 45 degrees.
|
|||
/// </summary>
|
|||
Directional45Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 135 degrees.
|
|||
/// </summary>
|
|||
Directional135Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 113 degrees.
|
|||
/// </summary>
|
|||
Directional113Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 157 degrees.
|
|||
/// </summary>
|
|||
Directional157Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 203 degrees.
|
|||
/// </summary>
|
|||
Directional203Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Projects neighboring samples into the block at 67 degrees.
|
|||
/// </summary>
|
|||
Directional67Degrees, |
|||
|
|||
/// <summary>
|
|||
/// Blends horizontal and vertical smooth predictions.
|
|||
/// </summary>
|
|||
Smooth, |
|||
|
|||
/// <summary>
|
|||
/// Interpolates vertically between the top row and the bottom-left neighbor.
|
|||
/// </summary>
|
|||
SmoothVertical, |
|||
|
|||
/// <summary>
|
|||
/// Interpolates horizontally between the left column and the top-right neighbor.
|
|||
/// </summary>
|
|||
SmoothHorizontal, |
|||
|
|||
/// <summary>
|
|||
/// Selects the neighbor with the smallest gradient from the top-left reference.
|
|||
/// </summary>
|
|||
Paeth, |
|||
|
|||
/// <summary>
|
|||
/// Predicts chroma from the reconstructed luma AC surface.
|
|||
/// </summary>
|
|||
ChromaFromLuma, |
|||
|
|||
/// <summary>
|
|||
/// The exclusive upper bound of valid chroma intra-prediction modes.
|
|||
/// </summary>
|
|||
ModeCount, |
|||
|
|||
/// <summary>
|
|||
/// Identifies an unavailable chroma prediction mode on an inter-predicted block.
|
|||
/// </summary>
|
|||
Invalid, |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <summary>
|
|||
/// Provides luma-equivalent prediction metadata for AV1 chroma intra-prediction modes.
|
|||
/// </summary>
|
|||
internal static class Av1ChromaPredictionModeExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Maps a chroma intra-prediction mode to the equivalent luma intra-prediction mode.
|
|||
/// </summary>
|
|||
/// <param name="mode">The chroma intra-prediction mode.</param>
|
|||
/// <returns>The luma mode with the same spatial predictor, or the invalid luma sentinel for an invalid chroma mode.</returns>
|
|||
public static Av1PredictionMode ToLumaMode(this Av1ChromaPredictionMode mode) |
|||
=> mode switch |
|||
{ |
|||
Av1ChromaPredictionMode.DC => Av1PredictionMode.DC, |
|||
Av1ChromaPredictionMode.Vertical => Av1PredictionMode.Vertical, |
|||
Av1ChromaPredictionMode.Horizontal => Av1PredictionMode.Horizontal, |
|||
Av1ChromaPredictionMode.Directional45Degrees => Av1PredictionMode.Directional45Degrees, |
|||
Av1ChromaPredictionMode.Directional135Degrees => Av1PredictionMode.Directional135Degrees, |
|||
Av1ChromaPredictionMode.Directional113Degrees => Av1PredictionMode.Directional113Degrees, |
|||
Av1ChromaPredictionMode.Directional157Degrees => Av1PredictionMode.Directional157Degrees, |
|||
Av1ChromaPredictionMode.Directional203Degrees => Av1PredictionMode.Directional203Degrees, |
|||
Av1ChromaPredictionMode.Directional67Degrees => Av1PredictionMode.Directional67Degrees, |
|||
Av1ChromaPredictionMode.Smooth => Av1PredictionMode.Smooth, |
|||
Av1ChromaPredictionMode.SmoothVertical => Av1PredictionMode.SmoothVertical, |
|||
Av1ChromaPredictionMode.SmoothHorizontal => Av1PredictionMode.SmoothHorizontal, |
|||
Av1ChromaPredictionMode.Paeth => Av1PredictionMode.Paeth, |
|||
|
|||
// Chroma-from-luma adds its AC contribution to a DC prediction. libaom's get_uv_mode() therefore maps it
|
|||
// to DC when shared transform and neighbor metadata require the corresponding luma predictor.
|
|||
Av1ChromaPredictionMode.ChromaFromLuma => Av1PredictionMode.DC, |
|||
_ => Av1PredictionMode.IntraInvalid, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Determines whether a chroma intra-prediction mode projects samples along a coded angle.
|
|||
/// </summary>
|
|||
/// <param name="mode">The chroma intra-prediction mode.</param>
|
|||
/// <returns><see langword="true"/> for a directional mode; otherwise, <see langword="false"/>.</returns>
|
|||
public static bool IsDirectional(this Av1ChromaPredictionMode mode) |
|||
=> mode is >= Av1ChromaPredictionMode.Vertical and <= Av1ChromaPredictionMode.Directional67Degrees; |
|||
} |
|||
@ -1,25 +0,0 @@ |
|||
// 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; } |
|||
} |
|||
} |
|||
@ -1,183 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; |
|||
|
|||
/// <content>
|
|||
/// Defines the neighbor-usage flags and scalar/SIMD contract for closed intra-prediction operators.
|
|||
/// </content>
|
|||
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,78 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Defines the closed interpolation operators used by intra-block-copy prediction.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Averages horizontally adjacent source samples for a half-sample horizontal phase.
|
|||
/// </summary>
|
|||
private readonly struct HorizontalOperator : IAv1IntraBlockCopyOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesRight => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesBottom => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte topLeft, byte topRight, byte bottomLeft, byte bottomRight) => (byte)((topLeft + topRight + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter( |
|||
Vector128<byte> topLeft, |
|||
Vector128<byte> topRight, |
|||
Vector128<byte> bottomLeft, |
|||
Vector128<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter( |
|||
Vector256<byte> topLeft, |
|||
Vector256<byte> topRight, |
|||
Vector256<byte> bottomLeft, |
|||
Vector256<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter( |
|||
Vector512<byte> topLeft, |
|||
Vector512<byte> topRight, |
|||
Vector512<byte> bottomLeft, |
|||
Vector512<byte> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short topLeft, short topRight, short bottomLeft, short bottomRight) => (short)((topLeft + topRight + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter( |
|||
Vector128<short> topLeft, |
|||
Vector128<short> topRight, |
|||
Vector128<short> bottomLeft, |
|||
Vector128<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter( |
|||
Vector256<short> topLeft, |
|||
Vector256<short> topRight, |
|||
Vector256<short> bottomLeft, |
|||
Vector256<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter( |
|||
Vector512<short> topLeft, |
|||
Vector512<short> topRight, |
|||
Vector512<short> bottomLeft, |
|||
Vector512<short> bottomRight) |
|||
=> AverageRounded(topLeft, topRight); |
|||
} |
|||
} |
|||
@ -1,136 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
/// <content>
|
|||
/// Defines the scalar and SIMD contract for closed intra-block-copy filter operators.
|
|||
/// </content>
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Defines lane-wise arithmetic for one intra-block-copy filter phase.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Every SIMD lane corresponds to one output column. The generic traversal supplies the integer source sample and
|
|||
/// its right, lower, and lower-right neighbors; closed operator types allow the JIT to remove unused source loads.
|
|||
/// </remarks>
|
|||
private interface IOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operator consumes the source sample to the right.
|
|||
/// </summary>
|
|||
public static abstract bool UsesRight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operator consumes the source sample on the following row.
|
|||
/// </summary>
|
|||
public static abstract bool UsesBottom { get; } |
|||
|
|||
/// <summary>
|
|||
/// Filters one 8-bit sample.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source sample.</param>
|
|||
/// <param name="topRight">The source sample one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source sample one row below.</param>
|
|||
/// <param name="bottomRight">The source sample one row below and one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit sample.</returns>
|
|||
public static abstract byte Filter(byte topLeft, byte topRight, byte bottomLeft, byte bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector128<byte> Filter( |
|||
Vector128<byte> topLeft, |
|||
Vector128<byte> topRight, |
|||
Vector128<byte> bottomLeft, |
|||
Vector128<byte> bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector256<byte> Filter( |
|||
Vector256<byte> topLeft, |
|||
Vector256<byte> topRight, |
|||
Vector256<byte> bottomLeft, |
|||
Vector256<byte> bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixty-four 8-bit samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered 8-bit samples.</returns>
|
|||
public static abstract Vector512<byte> Filter( |
|||
Vector512<byte> topLeft, |
|||
Vector512<byte> topRight, |
|||
Vector512<byte> bottomLeft, |
|||
Vector512<byte> bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters one high-bit-depth sample.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source sample.</param>
|
|||
/// <param name="topRight">The source sample one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source sample one row below.</param>
|
|||
/// <param name="bottomRight">The source sample one row below and one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth sample.</returns>
|
|||
public static abstract short Filter(short topLeft, short topRight, short bottomLeft, short bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters eight high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector128<short> Filter( |
|||
Vector128<short> topLeft, |
|||
Vector128<short> topRight, |
|||
Vector128<short> bottomLeft, |
|||
Vector128<short> bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters sixteen high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector256<short> Filter( |
|||
Vector256<short> topLeft, |
|||
Vector256<short> topRight, |
|||
Vector256<short> bottomLeft, |
|||
Vector256<short> bottomRight); |
|||
|
|||
/// <summary>
|
|||
/// Filters thirty-two high-bit-depth samples in parallel.
|
|||
/// </summary>
|
|||
/// <param name="topLeft">The integer-position source samples.</param>
|
|||
/// <param name="topRight">The source samples one column to the right.</param>
|
|||
/// <param name="bottomLeft">The source samples one row below.</param>
|
|||
/// <param name="bottomRight">The source samples one row below and one column to the right.</param>
|
|||
/// <returns>The filtered high-bit-depth samples.</returns>
|
|||
public static abstract Vector512<short> Filter( |
|||
Vector512<short> topLeft, |
|||
Vector512<short> topRight, |
|||
Vector512<short> bottomLeft, |
|||
Vector512<short> bottomRight); |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.IntraBlockCopy; |
|||
|
|||
internal static partial class Av1IntraBlockCopyPredictor |
|||
{ |
|||
/// <summary>
|
|||
/// Averages vertically adjacent source samples for a half-sample vertical phase.
|
|||
/// </summary>
|
|||
private readonly struct VerticalOperator : IAv1IntraBlockCopyOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesRight => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool UsesBottom => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static byte Filter(byte topLeft, byte topRight, byte bottomLeft, byte bottomRight) => (byte)((topLeft + bottomLeft + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<byte> Filter( |
|||
Vector128<byte> topLeft, |
|||
Vector128<byte> topRight, |
|||
Vector128<byte> bottomLeft, |
|||
Vector128<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<byte> Filter( |
|||
Vector256<byte> topLeft, |
|||
Vector256<byte> topRight, |
|||
Vector256<byte> bottomLeft, |
|||
Vector256<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<byte> Filter( |
|||
Vector512<byte> topLeft, |
|||
Vector512<byte> topRight, |
|||
Vector512<byte> bottomLeft, |
|||
Vector512<byte> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static short Filter(short topLeft, short topRight, short bottomLeft, short bottomRight) => (short)((topLeft + bottomLeft + 1) >> 1); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<short> Filter( |
|||
Vector128<short> topLeft, |
|||
Vector128<short> topRight, |
|||
Vector128<short> bottomLeft, |
|||
Vector128<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<short> Filter( |
|||
Vector256<short> topLeft, |
|||
Vector256<short> topRight, |
|||
Vector256<short> bottomLeft, |
|||
Vector256<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<short> Filter( |
|||
Vector512<short> topLeft, |
|||
Vector512<short> topRight, |
|||
Vector512<short> bottomLeft, |
|||
Vector512<short> bottomRight) |
|||
=> AverageRounded(topLeft, bottomLeft); |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; |
|||
|
|||
/// <summary>
|
|||
/// Owns the completed decoded state retained for one AV1 reference or presentation frame.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Reference-map owners contain reconstruction samples after the normative in-loop filters and before film-grain
|
|||
/// synthesis. A presentation-only owner may instead contain the independently synthesized grained output.
|
|||
/// </remarks>
|
|||
internal sealed class Av1ReferenceFrame : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The completed sample planes while this instance owns them.
|
|||
/// </summary>
|
|||
private Av1FrameBuffer<byte>? frameBuffer; |
|||
|
|||
/// <summary>
|
|||
/// The independently retained entropy snapshot while this frame owner remains alive.
|
|||
/// </summary>
|
|||
private Av1FrameEntropyContext? entropyContext; |
|||
|
|||
/// <summary>
|
|||
/// The decoder-session owner that receives <see cref="entropyContext"/> when this frame is released.
|
|||
/// </summary>
|
|||
private Av1FrameEntropyContexts? entropyContextOwner; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ReferenceFrame"/> class and takes ownership of the decoded
|
|||
/// sample buffer.
|
|||
/// </summary>
|
|||
/// <param name="frameBuffer">
|
|||
/// The completed sample buffer. Ownership transfers to this instance when construction succeeds.
|
|||
/// </param>
|
|||
/// <param name="frameHeader">
|
|||
/// The completed frame header associated with the reconstructed samples. The caller must not mutate the header
|
|||
/// after transferring it to this instance.
|
|||
/// </param>
|
|||
/// <param name="frameInfo">
|
|||
/// The completed per-block state associated with the reconstructed samples. The caller must not mutate the state
|
|||
/// after transferring it to this instance.
|
|||
/// </param>
|
|||
public Av1ReferenceFrame(Av1FrameBuffer<byte> frameBuffer, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) |
|||
{ |
|||
this.frameBuffer = frameBuffer; |
|||
this.FrameHeader = frameHeader; |
|||
this.FrameInfo = frameInfo; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ReferenceFrame"/> class and takes ownership of decoded samples
|
|||
/// and the entropy snapshot retained by a refreshed reference frame.
|
|||
/// </summary>
|
|||
/// <param name="frameBuffer">
|
|||
/// The completed sample buffer. Ownership transfers to this instance when construction succeeds.
|
|||
/// </param>
|
|||
/// <param name="frameHeader">
|
|||
/// The completed frame header associated with the reconstructed samples. The caller must not mutate the header
|
|||
/// after transferring it to this instance.
|
|||
/// </param>
|
|||
/// <param name="frameInfo">
|
|||
/// The completed per-block state associated with the reconstructed samples. The caller must not mutate the state
|
|||
/// after transferring it to this instance.
|
|||
/// </param>
|
|||
/// <param name="entropyContext">The completed entropy snapshot selected for later primary-reference use.</param>
|
|||
/// <param name="entropyContextOwner">The decoder-session owner to which the snapshot is returned.</param>
|
|||
public Av1ReferenceFrame( |
|||
Av1FrameBuffer<byte> frameBuffer, |
|||
ObuFrameHeader frameHeader, |
|||
Av1FrameInfo frameInfo, |
|||
Av1FrameEntropyContext entropyContext, |
|||
Av1FrameEntropyContexts entropyContextOwner) |
|||
: this(frameBuffer, frameHeader, frameInfo) |
|||
{ |
|||
this.entropyContext = entropyContext; |
|||
this.entropyContextOwner = entropyContextOwner; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the completed sample buffer owned by this frame.
|
|||
/// </summary>
|
|||
public Av1FrameBuffer<byte> FrameBuffer => this.frameBuffer!; |
|||
|
|||
/// <summary>
|
|||
/// Gets the completed header that describes the retained frame.
|
|||
/// </summary>
|
|||
public ObuFrameHeader FrameHeader { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the decoded per-block mode, motion, transform, and filter state associated with the retained frame.
|
|||
/// </summary>
|
|||
public Av1FrameInfo FrameInfo { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the entropy context retained for primary-reference use, or <see langword="null"/> for a presentation-only
|
|||
/// frame.
|
|||
/// </summary>
|
|||
public Av1FrameEntropyContext? EntropyContext => this.entropyContext; |
|||
|
|||
/// <summary>
|
|||
/// Transfers the completed sample planes out of this frame owner.
|
|||
/// </summary>
|
|||
/// <returns>The completed sample planes now owned by the caller.</returns>
|
|||
public Av1FrameBuffer<byte> TakeFrameBuffer() |
|||
{ |
|||
Av1FrameBuffer<byte> result = this.frameBuffer!; |
|||
this.frameBuffer = null; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases the owned completed sample planes and returns any retained entropy snapshot to its decoder session.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
Av1FrameEntropyContext? context = this.entropyContext; |
|||
this.entropyContext = null; |
|||
if (context is not null) |
|||
{ |
|||
// Nulling the field before returning the graph makes repeated disposal harmless and guarantees that one
|
|||
// shared frame owner occupying multiple reference slots returns its snapshot exactly once.
|
|||
this.entropyContextOwner!.ReturnSnapshot(context); |
|||
this.entropyContextOwner = null; |
|||
} |
|||
|
|||
this.frameBuffer?.Dispose(); |
|||
this.frameBuffer = null; |
|||
} |
|||
} |
|||
@ -0,0 +1,111 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; |
|||
|
|||
/// <summary>
|
|||
/// Extends reconstructed AV1 edge samples through the padded reference-frame border.
|
|||
/// </summary>
|
|||
internal static class Av1ReferenceFrameBorder |
|||
{ |
|||
/// <summary>
|
|||
/// Replicates every visible plane edge through its complete decoder padding.
|
|||
/// </summary>
|
|||
/// <param name="frameBuffer">The post-restoration reference frame whose padding is extended.</param>
|
|||
public static void Extend(Av1FrameBuffer<byte> frameBuffer) |
|||
{ |
|||
ObuColorConfig colorConfig = frameBuffer.ColorConfig; |
|||
int subsamplingX = !colorConfig.IsMonochrome && colorConfig.SubSamplingX ? 1 : 0; |
|||
int subsamplingY = !colorConfig.IsMonochrome && colorConfig.SubSamplingY ? 1 : 0; |
|||
|
|||
ExtendPlane( |
|||
frameBuffer, |
|||
frameBuffer.BufferY!, |
|||
frameBuffer.OriginX, |
|||
frameBuffer.OriginY, |
|||
frameBuffer.Width, |
|||
frameBuffer.Height); |
|||
|
|||
if (!colorConfig.IsMonochrome) |
|||
{ |
|||
int chromaWidth = Av1Math.DivideLog2Ceiling(frameBuffer.Width, subsamplingX); |
|||
int chromaHeight = Av1Math.DivideLog2Ceiling(frameBuffer.Height, subsamplingY); |
|||
int chromaOriginX = frameBuffer.OriginX >> subsamplingX; |
|||
int chromaOriginY = frameBuffer.OriginY >> subsamplingY; |
|||
|
|||
ExtendPlane(frameBuffer, frameBuffer.BufferCb!, chromaOriginX, chromaOriginY, chromaWidth, chromaHeight); |
|||
ExtendPlane(frameBuffer, frameBuffer.BufferCr!, chromaOriginX, chromaOriginY, chromaWidth, chromaHeight); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Selects the native sample representation for one byte-backed plane.
|
|||
/// </summary>
|
|||
/// <param name="frameBuffer">The frame that defines the native sample size.</param>
|
|||
/// <param name="buffer">The padded plane allocation.</param>
|
|||
/// <param name="originX">The horizontal visible origin in plane samples.</param>
|
|||
/// <param name="originY">The vertical visible origin in rows.</param>
|
|||
/// <param name="width">The visible plane width.</param>
|
|||
/// <param name="height">The visible plane height.</param>
|
|||
private static void ExtendPlane( |
|||
Av1FrameBuffer<byte> frameBuffer, |
|||
Buffer2D<byte> buffer, |
|||
int originX, |
|||
int originY, |
|||
int width, |
|||
int height) |
|||
{ |
|||
if (frameBuffer.BytesPerSample == 2) |
|||
{ |
|||
ExtendPlane(MemoryMarshal.Cast<byte, ushort>(buffer.DangerousGetSingleSpan()), buffer.Width >> 1, originX, originY, width, height); |
|||
} |
|||
else |
|||
{ |
|||
ExtendPlane(buffer.DangerousGetSingleSpan(), buffer.Width, originX, originY, width, height); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extends one native sample plane horizontally and then vertically.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native eight-bit or high-bit-depth sample type.</typeparam>
|
|||
/// <param name="plane">The complete padded plane allocation.</param>
|
|||
/// <param name="stride">The number of native samples between adjacent rows.</param>
|
|||
/// <param name="originX">The horizontal visible origin in plane samples.</param>
|
|||
/// <param name="originY">The vertical visible origin in rows.</param>
|
|||
/// <param name="width">The visible plane width.</param>
|
|||
/// <param name="height">The visible plane height.</param>
|
|||
private static void ExtendPlane<TSample>(Span<TSample> plane, int stride, int originX, int originY, int width, int height) |
|||
where TSample : unmanaged |
|||
{ |
|||
int rightStart = originX + width; |
|||
int rightLength = stride - rightStart; |
|||
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
Span<TSample> destinationRow = plane.Slice((originY + row) * stride, stride); |
|||
|
|||
// Span.Fill maps these long constant runs to the runtime's vectorized fill implementation. Extending the
|
|||
// horizontal edges first also makes each later full-row copy include complete left and right padding.
|
|||
destinationRow[..originX].Fill(destinationRow[originX]); |
|||
destinationRow.Slice(rightStart, rightLength).Fill(destinationRow[rightStart - 1]); |
|||
} |
|||
|
|||
ReadOnlySpan<TSample> firstVisibleRow = plane.Slice(originY * stride, stride); |
|||
for (int row = 0; row < originY; row++) |
|||
{ |
|||
firstVisibleRow.CopyTo(plane.Slice(row * stride, stride)); |
|||
} |
|||
|
|||
int bottomStart = originY + height; |
|||
ReadOnlySpan<TSample> lastVisibleRow = plane.Slice((bottomStart - 1) * stride, stride); |
|||
for (int row = bottomStart; row < plane.Length / stride; row++) |
|||
{ |
|||
lastVisibleRow.CopyTo(plane.Slice(row * stride, stride)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,279 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; |
|||
|
|||
/// <summary>
|
|||
/// Derives the seven AV1 inter-reference map indices from short reference signaling.
|
|||
/// </summary>
|
|||
internal static class Av1ReferenceFrameDerivation |
|||
{ |
|||
/// <summary>
|
|||
/// The shifted-order sentinel used for a reference-map slot that is not available to the current frame.
|
|||
/// </summary>
|
|||
private const int UnavailableSortIndex = -1; |
|||
|
|||
/// <summary>
|
|||
/// The reference-type value subtracted when indexing the seven-entry inter-reference map.
|
|||
/// </summary>
|
|||
private const int ReferenceIndexOffset = (int)Av1ReferenceFrameType.Last; |
|||
|
|||
/// <summary>
|
|||
/// Gets the order in which unassigned backward roles are replaced by forward references.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<Av1ReferenceFrameType> RemainingReferenceOrder => |
|||
[ |
|||
Av1ReferenceFrameType.Last2, |
|||
Av1ReferenceFrameType.Last3, |
|||
Av1ReferenceFrameType.Backward, |
|||
Av1ReferenceFrameType.Alternate2, |
|||
Av1ReferenceFrameType.Alternate, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Derives the reference-map slot selected for each inter-reference type when an AV1 frame uses short reference
|
|||
/// signaling.
|
|||
/// </summary>
|
|||
/// <param name="currentOrderHint">The current frame order hint in the active modulo order-hint domain.</param>
|
|||
/// <param name="orderHintBitWidth">The number of bits in the active order-hint domain.</param>
|
|||
/// <param name="lastFrameIndex">The explicitly signaled reference-map slot for <see cref="Av1ReferenceFrameType.Last"/>.</param>
|
|||
/// <param name="goldenFrameIndex">The explicitly signaled reference-map slot for <see cref="Av1ReferenceFrameType.Golden"/>.</param>
|
|||
/// <param name="slotOrderHints">The eight persisted reference-map order hints.</param>
|
|||
/// <param name="slotOccupancy">
|
|||
/// The eight values indicating whether each persisted reference-map slot owns a decoded frame. An empty slot is
|
|||
/// excluded from derivation.
|
|||
/// </param>
|
|||
/// <param name="referenceFrameIndices">
|
|||
/// The destination for seven slot indices ordered from <see cref="Av1ReferenceFrameType.Last"/> through
|
|||
/// <see cref="Av1ReferenceFrameType.Alternate"/>.
|
|||
/// </param>
|
|||
/// <exception cref="InvalidImageContentException">
|
|||
/// The signaled LAST or GOLDEN slot is empty, refers to the current frame, or refers to a future frame.
|
|||
/// </exception>
|
|||
/// <remarks>
|
|||
/// The caller owns the fixed AV1 table-size invariants: <paramref name="slotOrderHints"/> and
|
|||
/// <paramref name="slotOccupancy"/> contain eight entries, while <paramref name="referenceFrameIndices"/> contains
|
|||
/// seven entries. The signaled indices and order hints have already been read from their bounded bit fields. Every
|
|||
/// destination entry is overwritten on success, and multiple reference types may select the same slot. Frame-ID
|
|||
/// validity is a separate conformance state that the caller checks for every resolved reference after derivation.
|
|||
/// </remarks>
|
|||
public static void DeriveShortSignaledReferences( |
|||
uint currentOrderHint, |
|||
int orderHintBitWidth, |
|||
uint lastFrameIndex, |
|||
uint goldenFrameIndex, |
|||
ReadOnlySpan<uint> slotOrderHints, |
|||
ReadOnlySpan<bool> slotOccupancy, |
|||
Span<uint> referenceFrameIndices) |
|||
{ |
|||
int lastMapIndex = (int)lastFrameIndex; |
|||
int goldenMapIndex = (int)goldenFrameIndex; |
|||
|
|||
if (!slotOccupancy[lastMapIndex]) |
|||
{ |
|||
// Unlike an unused empty slot, the explicitly signaled LAST slot must own a decoded frame before any
|
|||
// derived mapping can be consumed. libaom rejects the missing reference at this frame-header boundary.
|
|||
throw new InvalidImageContentException("An AV1 inter frame requests an unavailable LAST reference."); |
|||
} |
|||
|
|||
if (!slotOccupancy[goldenMapIndex]) |
|||
{ |
|||
// GOLDEN is the other explicitly signaled slot and has the same ownership requirement as LAST.
|
|||
throw new InvalidImageContentException("An AV1 inter frame requests an unavailable GOLDEN reference."); |
|||
} |
|||
|
|||
int currentFrameSortIndex = 1 << (orderHintBitWidth - 1); |
|||
int orderHintMask = currentFrameSortIndex - 1; |
|||
InlineArray8<ReferenceFrameInfo> referenceInfo = default; |
|||
int lastFrameSortIndex = UnavailableSortIndex; |
|||
int goldenFrameSortIndex = UnavailableSortIndex; |
|||
|
|||
for (int mapIndex = 0; mapIndex < Av1Constants.ReferenceFrameCount; mapIndex++) |
|||
{ |
|||
ref ReferenceFrameInfo info = ref referenceInfo[mapIndex]; |
|||
info.MapIndex = mapIndex; |
|||
info.SortIndex = UnavailableSortIndex; |
|||
|
|||
if (!slotOccupancy[mapIndex]) |
|||
{ |
|||
// libaom gives absent reference buffers sort index -1. Keeping empty managed slots in the same
|
|||
// leading partition prevents their stale order hints from participating in temporal selection.
|
|||
continue; |
|||
} |
|||
|
|||
int difference = (int)slotOrderHints[mapIndex] - (int)currentOrderHint; |
|||
|
|||
// get_relative_dist folds the unsigned order-hint difference into the signed half-open interval
|
|||
// [-2^(bits-1), 2^(bits-1)). Adding the half-range makes -1 available as the absence sentinel while valid
|
|||
// entries sort from zero through the complete modulo domain.
|
|||
difference = (difference & orderHintMask) - (difference & currentFrameSortIndex); |
|||
info.SortIndex = currentFrameSortIndex + difference; |
|||
|
|||
if (mapIndex == lastMapIndex) |
|||
{ |
|||
lastFrameSortIndex = info.SortIndex; |
|||
} |
|||
|
|||
if (mapIndex == goldenMapIndex) |
|||
{ |
|||
goldenFrameSortIndex = info.SortIndex; |
|||
} |
|||
} |
|||
|
|||
if (lastFrameSortIndex >= currentFrameSortIndex) |
|||
{ |
|||
throw new InvalidImageContentException("An AV1 inter frame requests a current or future frame as LAST."); |
|||
} |
|||
|
|||
if (goldenFrameSortIndex >= currentFrameSortIndex) |
|||
{ |
|||
throw new InvalidImageContentException("An AV1 inter frame requests a current or future frame as GOLDEN."); |
|||
} |
|||
|
|||
// libaom sorts first by shifted output order and then by reference-map index. The explicit tie break is
|
|||
// normative: equal order hints select the highest map index for latest references and the lowest for earliest
|
|||
// references. Insertion sort is bounded to eight inline entries and does not allocate or require general sort
|
|||
// infrastructure at the frame-header boundary.
|
|||
for (int index = 1; index < Av1Constants.ReferenceFrameCount; index++) |
|||
{ |
|||
ReferenceFrameInfo current = referenceInfo[index]; |
|||
int insertionIndex = index; |
|||
|
|||
while (insertionIndex > 0) |
|||
{ |
|||
ReferenceFrameInfo previous = referenceInfo[insertionIndex - 1]; |
|||
if (previous.SortIndex < current.SortIndex || |
|||
(previous.SortIndex == current.SortIndex && previous.MapIndex <= current.MapIndex)) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
referenceInfo[insertionIndex] = previous; |
|||
insertionIndex--; |
|||
} |
|||
|
|||
referenceInfo[insertionIndex] = current; |
|||
} |
|||
|
|||
InlineArray8<bool> assignedReferences = default; |
|||
int lastReferenceIndex = (int)Av1ReferenceFrameType.Last - ReferenceIndexOffset; |
|||
int goldenReferenceIndex = (int)Av1ReferenceFrameType.Golden - ReferenceIndexOffset; |
|||
referenceFrameIndices[lastReferenceIndex] = lastFrameIndex; |
|||
referenceFrameIndices[goldenReferenceIndex] = goldenFrameIndex; |
|||
assignedReferences[lastReferenceIndex] = true; |
|||
assignedReferences[goldenReferenceIndex] = true; |
|||
|
|||
int forwardStartIndex = 0; |
|||
int forwardEndIndex = Av1Constants.ReferenceFrameCount - 1; |
|||
|
|||
// Empty entries sort before every occupied shifted hint. The first current-or-future entry then divides the
|
|||
// remaining sorted table into forward references on the left and backward references on the right.
|
|||
for (int index = 0; index < Av1Constants.ReferenceFrameCount; index++) |
|||
{ |
|||
if (referenceInfo[index].SortIndex == UnavailableSortIndex) |
|||
{ |
|||
forwardStartIndex++; |
|||
continue; |
|||
} |
|||
|
|||
if (referenceInfo[index].SortIndex >= currentFrameSortIndex) |
|||
{ |
|||
forwardEndIndex = index - 1; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
int backwardStartIndex = forwardEndIndex + 1; |
|||
int backwardEndIndex = Av1Constants.ReferenceFrameCount - 1; |
|||
int alternateReferenceIndex = (int)Av1ReferenceFrameType.Alternate - ReferenceIndexOffset; |
|||
int backwardReferenceIndex = (int)Av1ReferenceFrameType.Backward - ReferenceIndexOffset; |
|||
int alternate2ReferenceIndex = (int)Av1ReferenceFrameType.Alternate2 - ReferenceIndexOffset; |
|||
|
|||
if (backwardStartIndex <= backwardEndIndex) |
|||
{ |
|||
// ALTREF receives the frame farthest into the future. The sorted-map-index tie break selects the highest
|
|||
// slot when multiple frames share that order hint, matching both the specification and libaom.
|
|||
referenceFrameIndices[alternateReferenceIndex] = (uint)referenceInfo[backwardEndIndex].MapIndex; |
|||
assignedReferences[alternateReferenceIndex] = true; |
|||
backwardEndIndex--; |
|||
} |
|||
|
|||
if (backwardStartIndex <= backwardEndIndex) |
|||
{ |
|||
// BWDREF receives the nearest future frame and therefore consumes the low end of the backward partition.
|
|||
referenceFrameIndices[backwardReferenceIndex] = (uint)referenceInfo[backwardStartIndex].MapIndex; |
|||
assignedReferences[backwardReferenceIndex] = true; |
|||
backwardStartIndex++; |
|||
} |
|||
|
|||
if (backwardStartIndex <= backwardEndIndex) |
|||
{ |
|||
// ALTREF2 receives the next-nearest remaining future frame. No further backward lookup follows, so the
|
|||
// lower boundary does not need to advance after this assignment.
|
|||
referenceFrameIndices[alternate2ReferenceIndex] = (uint)referenceInfo[backwardStartIndex].MapIndex; |
|||
assignedReferences[alternate2ReferenceIndex] = true; |
|||
} |
|||
|
|||
ReadOnlySpan<Av1ReferenceFrameType> remainingReferenceOrder = RemainingReferenceOrder; |
|||
int remainingIndex; |
|||
|
|||
for (remainingIndex = 0; remainingIndex < remainingReferenceOrder.Length; remainingIndex++) |
|||
{ |
|||
int referenceIndex = (int)remainingReferenceOrder[remainingIndex] - ReferenceIndexOffset; |
|||
if (assignedReferences[referenceIndex]) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// LAST and GOLDEN were already assigned explicitly and cannot be reused while an unassigned forward slot
|
|||
// remains. Moving from the high end chooses the remaining frames in anti-chronological order.
|
|||
while (forwardStartIndex <= forwardEndIndex && |
|||
(referenceInfo[forwardEndIndex].MapIndex == lastMapIndex || |
|||
referenceInfo[forwardEndIndex].MapIndex == goldenMapIndex)) |
|||
{ |
|||
forwardEndIndex--; |
|||
} |
|||
|
|||
if (forwardStartIndex > forwardEndIndex) |
|||
{ |
|||
break; |
|||
} |
|||
|
|||
referenceFrameIndices[referenceIndex] = (uint)referenceInfo[forwardEndIndex].MapIndex; |
|||
assignedReferences[referenceIndex] = true; |
|||
forwardEndIndex--; |
|||
} |
|||
|
|||
for (; remainingIndex < remainingReferenceOrder.Length; remainingIndex++) |
|||
{ |
|||
int referenceIndex = (int)remainingReferenceOrder[remainingIndex] - ReferenceIndexOffset; |
|||
if (assignedReferences[referenceIndex]) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// AV1 requires every unfilled role to reuse the earliest available forward frame. At least LAST and GOLDEN
|
|||
// are occupied forward references, so forwardStartIndex always identifies a usable slot at this point.
|
|||
referenceFrameIndices[referenceIndex] = (uint)referenceInfo[forwardStartIndex].MapIndex; |
|||
assignedReferences[referenceIndex] = true; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores one reference-map slot and its shifted order for fixed-size sorting.
|
|||
/// </summary>
|
|||
private struct ReferenceFrameInfo |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the zero-based slot in the eight-entry persisted reference map.
|
|||
/// </summary>
|
|||
public int MapIndex { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the order hint shifted around the current frame, or <see cref="UnavailableSortIndex"/> when unavailable.
|
|||
/// </summary>
|
|||
public int SortIndex { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,265 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; |
|||
|
|||
/// <summary>
|
|||
/// Owns the reference map and selected presentation output for one bounded AV1 decoder session.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Several slots and the selected output may identify the same <see cref="Av1ReferenceFrame"/>. The store preserves
|
|||
/// that sharing without allocating reference-count objects and releases a frame only after its final owning reference
|
|||
/// has been replaced or cleared. This type is not thread safe; one decoder session serializes commit and disposal.
|
|||
/// </remarks>
|
|||
internal sealed class Av1ReferenceFrameStore : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// The number of reference slots defined by the AV1 uncompressed frame header.
|
|||
/// </summary>
|
|||
private const int SlotCount = Av1Constants.ReferenceFrameCount; |
|||
|
|||
/// <summary>
|
|||
/// Stores the frame owner selected by each reference-map slot without allocating a managed array.
|
|||
/// </summary>
|
|||
private InlineArray8<Av1ReferenceFrame?> frames; |
|||
|
|||
/// <summary>
|
|||
/// The most recent shown frame retained for presentation at the end of the bounded image payload.
|
|||
/// </summary>
|
|||
private Av1ReferenceFrame? outputFrame; |
|||
|
|||
/// <summary>
|
|||
/// Gets the most recent shown frame retained for presentation.
|
|||
/// </summary>
|
|||
public Av1ReferenceFrame? OutputFrame => this.outputFrame; |
|||
|
|||
/// <summary>
|
|||
/// Resolves one reference-map slot.
|
|||
/// </summary>
|
|||
/// <param name="slot">The zero-based reference-map slot in the inclusive range 0 through 7.</param>
|
|||
/// <returns>The retained frame, or <see langword="null"/> when the slot has not been populated.</returns>
|
|||
public Av1ReferenceFrame? Resolve(int slot) => this.frames[slot]; |
|||
|
|||
/// <summary>
|
|||
/// Writes whether each reference-map slot currently owns a reconstructed frame.
|
|||
/// </summary>
|
|||
/// <param name="destination">The eight-entry destination receiving the current slot occupancy.</param>
|
|||
public void FillOccupancy(Span<bool> destination) |
|||
{ |
|||
// Physical ownership is intentionally independent from frame-ID validity. Short reference signaling sorts
|
|||
// every occupied slot first, then the uncompressed-header parser validates each derived role separately.
|
|||
for (int slot = 0; slot < SlotCount; slot++) |
|||
{ |
|||
destination[slot] = this.frames[slot] is not null; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Commits a completed frame to the reference map and, when shown, retains it for presentation.
|
|||
/// </summary>
|
|||
/// <param name="refreshFrameFlags">
|
|||
/// The mask whose bit <c>n</c> replaces reference-map slot <c>n</c>. Only the low eight bits describe AV1 slots.
|
|||
/// </param>
|
|||
/// <param name="frame">The completed frame to retain in every selected ownership role.</param>
|
|||
/// <param name="showFrame">Whether the completed frame replaces the previously retained presentation output.</param>
|
|||
/// <returns>
|
|||
/// <see langword="true"/> when the frame is retained as a reference or presentation output and ownership transfers
|
|||
/// to this store; otherwise <see langword="false"/>, in which case no state changes and the caller retains ownership.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// The caller must invoke this method only after reconstruction and all normative in-loop filters have completed.
|
|||
/// Once ownership transfers, the caller must not dispose the frame. A frame passed here must not already be owned by
|
|||
/// this store.
|
|||
/// </remarks>
|
|||
public bool Commit(uint refreshFrameFlags, Av1ReferenceFrame frame, bool showFrame) |
|||
{ |
|||
refreshFrameFlags &= byte.MaxValue; |
|||
if (refreshFrameFlags == 0 && !showFrame) |
|||
{ |
|||
// A hidden frame with a zero refresh mask has no remaining role in an image-decoder session.
|
|||
return false; |
|||
} |
|||
|
|||
InlineArray8<Av1ReferenceFrame?> replacedFrames = default; |
|||
Av1ReferenceFrame? replacedOutputFrame = showFrame ? this.outputFrame : null; |
|||
|
|||
// Capture displaced owners in inline storage, then publish the complete slot and output transition before
|
|||
// releasing anything. A shown frame may also occupy reference slots, so both ownership domains must change as
|
|||
// one operation.
|
|||
for (int slot = 0; slot < SlotCount; slot++) |
|||
{ |
|||
if ((refreshFrameFlags & (1U << slot)) != 0) |
|||
{ |
|||
replacedFrames[slot] = this.frames[slot]; |
|||
this.frames[slot] = frame; |
|||
} |
|||
} |
|||
|
|||
if (showFrame) |
|||
{ |
|||
this.outputFrame = frame; |
|||
} |
|||
|
|||
for (int replacedIndex = 0; replacedIndex < SlotCount; replacedIndex++) |
|||
{ |
|||
Av1ReferenceFrame? replacedFrame = replacedFrames[replacedIndex]; |
|||
|
|||
if (replacedFrame is null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (ReferenceEquals(replacedFrame, replacedOutputFrame)) |
|||
{ |
|||
// Let the displaced-output path release this shared owner after every slot candidate has been removed.
|
|||
replacedFrames[replacedIndex] = null; |
|||
continue; |
|||
} |
|||
|
|||
// A displaced frame remains owned when any unrefreshed slot or the selected output still references it.
|
|||
// Eight fixed slots make the bounded identity scan cheaper than allocated reference-count state.
|
|||
if (this.IsRetained(replacedFrame)) |
|||
{ |
|||
replacedFrames[replacedIndex] = null; |
|||
} |
|||
} |
|||
|
|||
DisposeUnique(ref replacedFrames); |
|||
|
|||
if (replacedOutputFrame is not null && !this.IsRetained(replacedOutputFrame)) |
|||
{ |
|||
replacedOutputFrame.Dispose(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replaces the selected presentation output with an independently owned completed frame.
|
|||
/// </summary>
|
|||
/// <param name="frame">The completed presentation frame whose ownership transfers to this store.</param>
|
|||
/// <remarks>
|
|||
/// This path is used when film grain requires presentation samples to differ from the ungrained reconstruction
|
|||
/// retained by the reference map.
|
|||
/// </remarks>
|
|||
public void CommitOutput(Av1ReferenceFrame frame) |
|||
{ |
|||
Av1ReferenceFrame? replacedFrame = this.outputFrame; |
|||
this.outputFrame = frame; |
|||
|
|||
// The previous output may still be retained by one or more reference slots. Release it only after publishing
|
|||
// the new output and confirming that no reference-map identity remains.
|
|||
if (replacedFrame is not null && !this.IsRetained(replacedFrame)) |
|||
{ |
|||
replacedFrame.Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Transfers the selected presentation frame out of this store and releases every other retained frame.
|
|||
/// </summary>
|
|||
/// <returns>The selected presentation frame now owned by the caller.</returns>
|
|||
public Av1ReferenceFrame TakeOutput() |
|||
{ |
|||
Av1ReferenceFrame result = this.outputFrame!; |
|||
this.outputFrame = null; |
|||
|
|||
// The caller becomes the sole owner of the selected output. Remove all slot aliases before Reset releases the
|
|||
// remaining session references so the sample buffer can transfer without copying.
|
|||
for (int slot = 0; slot < SlotCount; slot++) |
|||
{ |
|||
if (ReferenceEquals(this.frames[slot], result)) |
|||
{ |
|||
this.frames[slot] = null; |
|||
} |
|||
} |
|||
|
|||
this.Reset(); |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears all reference-map slots and releases every uniquely retained frame.
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
InlineArray8<Av1ReferenceFrame?> releasedFrames = this.frames; |
|||
this.frames = default; |
|||
Av1ReferenceFrame? releasedOutputFrame = this.outputFrame; |
|||
this.outputFrame = null; |
|||
|
|||
// Clear the live map before disposal so the store cannot expose a partially reset ownership state. When the
|
|||
// output aliases a slot, let the output path perform the single release after the duplicate slot is removed.
|
|||
if (releasedOutputFrame is not null) |
|||
{ |
|||
for (int slot = 0; slot < SlotCount; slot++) |
|||
{ |
|||
if (ReferenceEquals(releasedFrames[slot], releasedOutputFrame)) |
|||
{ |
|||
releasedFrames[slot] = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
DisposeUnique(ref releasedFrames); |
|||
releasedOutputFrame?.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases every uniquely retained frame and clears all reference-map slots.
|
|||
/// </summary>
|
|||
public void Dispose() => this.Reset(); |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the live reference map or presentation output retains a frame.
|
|||
/// </summary>
|
|||
/// <param name="frame">The frame whose ownership is queried.</param>
|
|||
/// <returns><see langword="true"/> when the store still owns the frame.</returns>
|
|||
private bool IsRetained(Av1ReferenceFrame frame) |
|||
{ |
|||
if (ReferenceEquals(this.outputFrame, frame)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
for (int slot = 0; slot < SlotCount; slot++) |
|||
{ |
|||
if (ReferenceEquals(this.frames[slot], frame)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases each distinct frame owner in a fixed-size set exactly once.
|
|||
/// </summary>
|
|||
/// <param name="frames">The inline set of frame references to release.</param>
|
|||
private static void DisposeUnique(ref InlineArray8<Av1ReferenceFrame?> frames) |
|||
{ |
|||
for (int frameIndex = 0; frameIndex < SlotCount; frameIndex++) |
|||
{ |
|||
Av1ReferenceFrame? frame = frames[frameIndex]; |
|||
|
|||
if (frame is null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// Null every later alias before disposal. The store intentionally represents shared slot ownership through
|
|||
// object identity, so no separately allocated reference-count state is needed for the eight-entry map.
|
|||
for (int duplicateIndex = frameIndex + 1; duplicateIndex < SlotCount; duplicateIndex++) |
|||
{ |
|||
if (ReferenceEquals(frames[duplicateIndex], frame)) |
|||
{ |
|||
frames[duplicateIndex] = null; |
|||
} |
|||
} |
|||
|
|||
frames[frameIndex] = null; |
|||
frame.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the blending method used to combine two AV1 inter predictors.
|
|||
/// </summary>
|
|||
internal enum Av1CompoundType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Averages both predictors with equal weights.
|
|||
/// </summary>
|
|||
Average = 0, |
|||
|
|||
/// <summary>
|
|||
/// Weights predictors from their relative display-order distances.
|
|||
/// </summary>
|
|||
DistanceWeighted = 1, |
|||
|
|||
/// <summary>
|
|||
/// Selects per-pixel weights from a signaled wedge mask.
|
|||
/// </summary>
|
|||
Wedge = 2, |
|||
|
|||
/// <summary>
|
|||
/// Derives per-pixel weights from the difference between both predictors.
|
|||
/// </summary>
|
|||
DifferenceWeighted = 3, |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the orientation of an AV1 difference-weighted compound mask.
|
|||
/// </summary>
|
|||
internal enum Av1DifferenceWeightedMaskType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the predictor-difference adjustment to a base alpha weight of 38 on AV1's 0-through-64 blend scale.
|
|||
/// </summary>
|
|||
Type38 = 0, |
|||
|
|||
/// <summary>
|
|||
/// Applies the complement of the type-38 predictor-difference mask.
|
|||
/// </summary>
|
|||
Type38Inverse = 1, |
|||
} |
|||
@ -0,0 +1,536 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Owns the retained and projected per-8x8 motion fields associated with a decoded AV1 frame.
|
|||
/// </summary>
|
|||
internal partial class Av1FrameInfo |
|||
{ |
|||
/// <summary>
|
|||
/// The maximum absolute temporal distance accepted by AV1 motion-field projection.
|
|||
/// </summary>
|
|||
private const int MaximumFrameDistance = 31; |
|||
|
|||
/// <summary>
|
|||
/// The maximum number of reference frames projected into one temporal motion field.
|
|||
/// </summary>
|
|||
private const int MotionFieldProjectionCount = 3; |
|||
|
|||
/// <summary>
|
|||
/// The maximum source motion-vector magnitude retained for later temporal projection.
|
|||
/// </summary>
|
|||
private const int ReferenceMotionVectorLimit = 4095; |
|||
|
|||
/// <summary>
|
|||
/// The exclusive upper bound of an AV1 motion-vector component in one-eighth-sample units.
|
|||
/// </summary>
|
|||
private const int MotionVectorUpperBound = 16384; |
|||
|
|||
/// <summary>
|
|||
/// The reserved lower endpoint of an AV1 motion-vector component in one-eighth-sample units.
|
|||
/// </summary>
|
|||
private const int MotionVectorLowerBound = -16384; |
|||
|
|||
/// <summary>
|
|||
/// The width or height of the largest AV1 superblock in 4x4 mode-information units.
|
|||
/// </summary>
|
|||
private const int MaximumSuperblockModeInfoSize = 1 << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2); |
|||
|
|||
/// <summary>
|
|||
/// The base-two logarithm of <see cref="MaximumSuperblockModeInfoSize"/>.
|
|||
/// </summary>
|
|||
private const int MaximumSuperblockModeInfoSizeLog2 = Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2; |
|||
|
|||
/// <summary>
|
|||
/// The base-two reduction from 4x4 mode-information coordinates to the 8x8 motion-field grid.
|
|||
/// </summary>
|
|||
private const int MotionFieldModeInfoShift = 1; |
|||
|
|||
/// <summary>
|
|||
/// The base-two reduction from one-eighth-sample motion vectors to offsets on the 8x8 motion-field grid.
|
|||
/// </summary>
|
|||
private const int MotionVectorToFieldOffsetShift = 4 + Av1Constants.ModeInfoSizeLog2; |
|||
|
|||
/// <summary>
|
|||
/// The maximum horizontal projection displacement, measured in 8x8 motion-field blocks.
|
|||
/// </summary>
|
|||
private const int MaximumHorizontalFieldOffset = 8; |
|||
|
|||
/// <summary>
|
|||
/// Stores the selected motion vector and logical reference for every retained 8x8 frame position.
|
|||
/// </summary>
|
|||
private RetainedMotionFieldEntry[] retainedMotionField = []; |
|||
|
|||
/// <summary>
|
|||
/// Stores motion vectors projected from retained frames into the current frame's 8x8 grid.
|
|||
/// </summary>
|
|||
private TemporalMotionFieldEntry[] temporalMotionField = []; |
|||
|
|||
/// <summary>
|
|||
/// Stores the order hint selected by each logical inter-reference type for later projections from this frame.
|
|||
/// </summary>
|
|||
private InlineArray8<uint> motionFieldReferenceOrderHints; |
|||
|
|||
/// <summary>
|
|||
/// Stores whether each logical inter-reference type lies after, at, or before the current frame in display order.
|
|||
/// </summary>
|
|||
private InlineArray8<sbyte> motionFieldReferenceSides; |
|||
|
|||
/// <summary>
|
|||
/// The number of retained motion-field entries in one active 8x8 row.
|
|||
/// </summary>
|
|||
private int retainedMotionFieldStride; |
|||
|
|||
/// <summary>
|
|||
/// The number of projected temporal-motion entries in one aligned 8x8 row.
|
|||
/// </summary>
|
|||
private int temporalMotionFieldStride; |
|||
|
|||
/// <summary>
|
|||
/// The active frame width in 4x4 mode-information units.
|
|||
/// </summary>
|
|||
private int activeModeInfoColumnCount; |
|||
|
|||
/// <summary>
|
|||
/// The active frame height in 4x4 mode-information units.
|
|||
/// </summary>
|
|||
private int activeModeInfoRowCount; |
|||
|
|||
/// <summary>
|
|||
/// Gets the reciprocal table used by AV1 motion-vector projection in 14-bit fixed-point precision.
|
|||
/// </summary>
|
|||
private static ReadOnlySpan<int> ProjectionDivisors => |
|||
[0, 16384, 8192, 5461, 4096, 3276, 2730, 2340, 2048, 1820, 1638, 1489, 1365, 1260, 1170, 1092, |
|||
1024, 963, 910, 862, 819, 780, 744, 712, 682, 655, 630, 606, 585, 564, 546, 528]; |
|||
|
|||
/// <summary>
|
|||
/// Allocates and derives the motion fields required by one decoded frame.
|
|||
/// </summary>
|
|||
/// <param name="sequenceHeader">The sequence header defining motion-field enablement and order-hint precision.</param>
|
|||
/// <param name="frameHeader">The current frame header and its seven resolved inter-reference roles.</param>
|
|||
/// <param name="referenceFrames">The retained reconstructed frames selected by the current reference map.</param>
|
|||
public void InitializeMotionField( |
|||
ObuSequenceHeader sequenceHeader, |
|||
ObuFrameHeader frameHeader, |
|||
Av1ReferenceFrameStore referenceFrames) |
|||
{ |
|||
if (!sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.activeModeInfoColumnCount = frameHeader.ModeInfoColumnCount; |
|||
this.activeModeInfoRowCount = frameHeader.ModeInfoRowCount; |
|||
this.retainedMotionFieldStride = (this.activeModeInfoColumnCount + 1) >> MotionFieldModeInfoShift; |
|||
|
|||
if (frameHeader.IsIntra) |
|||
{ |
|||
// Intra frames retain an empty source field. They can occupy reference slots, but libaom rejects them as
|
|||
// projection sources before consulting their reference-order-hint metadata.
|
|||
return; |
|||
} |
|||
|
|||
InlineArray8<Av1ReferenceFrame?> selectedReferences = default; |
|||
Span<uint> referenceFrameIndices = frameHeader.GetReferenceFrameIndices(); |
|||
int orderHintBits = sequenceHeader.OrderHintInfo.OrderHintBits; |
|||
|
|||
// Capture the seven logical-role order hints before this frame refreshes any physical map slots. Libaom keeps
|
|||
// the same snapshot on RefCntBuffer so a later frame can project this frame's stored motion vectors.
|
|||
for (int referenceIndex = 0; referenceIndex < Av1Constants.ReferencesPerFrame; referenceIndex++) |
|||
{ |
|||
Av1ReferenceFrameType referenceFrameType = (Av1ReferenceFrameType)(referenceIndex + 1); |
|||
Av1ReferenceFrame referenceFrame = referenceFrames.Resolve((int)referenceFrameIndices[referenceIndex])!; |
|||
uint referenceOrderHint = referenceFrame.FrameHeader.OrderHint; |
|||
selectedReferences[(int)referenceFrameType] = referenceFrame; |
|||
this.motionFieldReferenceOrderHints[(int)referenceFrameType] = referenceOrderHint; |
|||
|
|||
int relativeDistance = GetRelativeDistance(referenceOrderHint, frameHeader.OrderHint, orderHintBits); |
|||
this.motionFieldReferenceSides[(int)referenceFrameType] = relativeDistance > 0 |
|||
? (sbyte)1 |
|||
: referenceOrderHint == frameHeader.OrderHint ? (sbyte)-1 : (sbyte)0; |
|||
} |
|||
|
|||
// FrameInfo is transferred directly into each retained frame owner, so allocate only the active 8x8 source
|
|||
// grid whose completed block vectors can be projected by a later frame.
|
|||
int retainedRowCount = (this.activeModeInfoRowCount + 1) >> MotionFieldModeInfoShift; |
|||
this.retainedMotionField = new RetainedMotionFieldEntry[this.retainedMotionFieldStride * retainedRowCount]; |
|||
|
|||
if (!frameHeader.UseReferenceFrameMotionVectors) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// libaom aligns the projected field stride to the largest superblock even for a 64x64 sequence. This keeps
|
|||
// later temporal-candidate addressing independent of the current sequence's selected superblock size.
|
|||
int alignedModeInfoColumnCount = Av1Math.AlignPowerOf2( |
|||
this.activeModeInfoColumnCount, |
|||
MaximumSuperblockModeInfoSizeLog2); |
|||
|
|||
this.temporalMotionFieldStride = alignedModeInfoColumnCount >> MotionFieldModeInfoShift; |
|||
int temporalRowCount = (this.activeModeInfoRowCount + MaximumSuperblockModeInfoSize) >> MotionFieldModeInfoShift; |
|||
this.temporalMotionField = new TemporalMotionFieldEntry[this.temporalMotionFieldStride * temporalRowCount]; |
|||
|
|||
// AV1 examines LAST, BWDREF, ALTREF2, ALTREF, and LAST2 in this normative order and admits at most three
|
|||
// projection sources. LAST always consumes the first budget position, forward references consume one only
|
|||
// when eligible projection succeeds, and LAST2 fills the final unused position in the reverse direction.
|
|||
int remainingProjectionCount = MotionFieldProjectionCount; |
|||
Av1ReferenceFrame lastFrame = selectedReferences[(int)Av1ReferenceFrameType.Last]!; |
|||
Av1ReferenceFrame goldenFrame = selectedReferences[(int)Av1ReferenceFrameType.Golden]!; |
|||
uint alternateOfLastOrderHint = lastFrame.FrameInfo.motionFieldReferenceOrderHints[(int)Av1ReferenceFrameType.Alternate]; |
|||
|
|||
// A LAST frame whose ALTREF order matches GOLDEN is an overlay. Projecting it would duplicate the overlay's
|
|||
// temporal source, but libaom still consumes one position from the three-source projection budget.
|
|||
if (alternateOfLastOrderHint != goldenFrame.FrameHeader.OrderHint) |
|||
{ |
|||
_ = this.ProjectMotionField(sequenceHeader, frameHeader, lastFrame, reverseDirection: true); |
|||
} |
|||
|
|||
remainingProjectionCount--; |
|||
Av1ReferenceFrame backwardFrame = selectedReferences[(int)Av1ReferenceFrameType.Backward]!; |
|||
|
|||
if (GetRelativeDistance(backwardFrame.FrameHeader.OrderHint, frameHeader.OrderHint, orderHintBits) > 0 && |
|||
this.ProjectMotionField(sequenceHeader, frameHeader, backwardFrame, reverseDirection: false)) |
|||
{ |
|||
remainingProjectionCount--; |
|||
} |
|||
|
|||
Av1ReferenceFrame alternate2Frame = selectedReferences[(int)Av1ReferenceFrameType.Alternate2]!; |
|||
|
|||
if (GetRelativeDistance(alternate2Frame.FrameHeader.OrderHint, frameHeader.OrderHint, orderHintBits) > 0 && |
|||
this.ProjectMotionField(sequenceHeader, frameHeader, alternate2Frame, reverseDirection: false)) |
|||
{ |
|||
remainingProjectionCount--; |
|||
} |
|||
|
|||
Av1ReferenceFrame alternateFrame = selectedReferences[(int)Av1ReferenceFrameType.Alternate]!; |
|||
|
|||
if (remainingProjectionCount > 0 && |
|||
GetRelativeDistance(alternateFrame.FrameHeader.OrderHint, frameHeader.OrderHint, orderHintBits) > 0 && |
|||
this.ProjectMotionField(sequenceHeader, frameHeader, alternateFrame, reverseDirection: false)) |
|||
{ |
|||
remainingProjectionCount--; |
|||
} |
|||
|
|||
if (remainingProjectionCount > 0) |
|||
{ |
|||
Av1ReferenceFrame last2Frame = selectedReferences[(int)Av1ReferenceFrameType.Last2]!; |
|||
_ = this.ProjectMotionField(sequenceHeader, frameHeader, last2Frame, reverseDirection: true); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the temporal motion vector projected over a 4x4 mode-information position.
|
|||
/// </summary>
|
|||
/// <param name="modeInfoRow">The zero-based 4x4 row.</param>
|
|||
/// <param name="modeInfoColumn">The zero-based 4x4 column.</param>
|
|||
/// <param name="motionVector">Receives the retained source vector in one-eighth-sample units.</param>
|
|||
/// <param name="referenceFrameOffset">Receives the positive temporal distance from the source to its reference.</param>
|
|||
/// <returns><see langword="true"/> when a projected vector covers the requested position.</returns>
|
|||
public bool TryGetTemporalMotionVector( |
|||
int modeInfoRow, |
|||
int modeInfoColumn, |
|||
out Av1MotionVector motionVector, |
|||
out int referenceFrameOffset) |
|||
{ |
|||
int index = ((modeInfoRow >> MotionFieldModeInfoShift) * this.temporalMotionFieldStride) + |
|||
(modeInfoColumn >> MotionFieldModeInfoShift); |
|||
|
|||
TemporalMotionFieldEntry entry = this.temporalMotionField[index]; |
|||
motionVector = entry.MotionVector; |
|||
referenceFrameOffset = entry.ReferenceFrameOffset; |
|||
return referenceFrameOffset > 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the retained per-8x8 motion-field entries covered by one completed mode-information block.
|
|||
/// </summary>
|
|||
/// <param name="modeInfo">The completed block mode information.</param>
|
|||
/// <param name="modeInfoPosition">The block origin in frame-relative 4x4 units.</param>
|
|||
private void UpdateRetainedMotionField(Av1BlockModeInfo modeInfo, Point modeInfoPosition) |
|||
{ |
|||
if (this.retainedMotionField.Length == 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Av1ReferenceFrameType selectedReference = Av1ReferenceFrameType.None; |
|||
Av1MotionVector selectedMotionVector = default; |
|||
Span<Av1ReferenceFrameType> referenceFrames = modeInfo.ReferenceFrames; |
|||
Span<Av1MotionVector> motionVectors = modeInfo.MotionVectors; |
|||
|
|||
// Compound blocks may offer two vectors. libaom retains the last eligible forward-or-past reference after
|
|||
// excluding same-order, future, and out-of-range vectors, so preserve that overwrite order exactly.
|
|||
for (int referenceIndex = 0; referenceIndex < 2; referenceIndex++) |
|||
{ |
|||
Av1ReferenceFrameType referenceFrame = referenceFrames[referenceIndex]; |
|||
Av1MotionVector motionVector = motionVectors[referenceIndex]; |
|||
|
|||
if (referenceFrame > Av1ReferenceFrameType.Intra && |
|||
this.motionFieldReferenceSides[(int)referenceFrame] == 0 && |
|||
Math.Abs(motionVector.Row) <= ReferenceMotionVectorLimit && |
|||
Math.Abs(motionVector.Column) <= ReferenceMotionVectorLimit) |
|||
{ |
|||
selectedReference = referenceFrame; |
|||
selectedMotionVector = motionVector; |
|||
} |
|||
} |
|||
|
|||
int blockModeInfoWidth = Math.Min( |
|||
modeInfo.BlockSize.Get4x4WideCount(), |
|||
this.activeModeInfoColumnCount - modeInfoPosition.X); |
|||
|
|||
int blockModeInfoHeight = Math.Min( |
|||
modeInfo.BlockSize.Get4x4HighCount(), |
|||
this.activeModeInfoRowCount - modeInfoPosition.Y); |
|||
|
|||
int fieldWidth = (blockModeInfoWidth + 1) >> MotionFieldModeInfoShift; |
|||
int fieldHeight = (blockModeInfoHeight + 1) >> MotionFieldModeInfoShift; |
|||
int firstFieldRow = modeInfoPosition.Y >> MotionFieldModeInfoShift; |
|||
int firstFieldColumn = modeInfoPosition.X >> MotionFieldModeInfoShift; |
|||
RetainedMotionFieldEntry entry = new(selectedMotionVector, selectedReference); |
|||
|
|||
// One decoded block supplies the same retained candidate to every covered 8x8 cell. Array.Fill preserves the
|
|||
// native contiguous-row write and lets later sub-8x8 blocks overwrite the shared cell in traversal order.
|
|||
for (int row = 0; row < fieldHeight; row++) |
|||
{ |
|||
int rowOffset = ((firstFieldRow + row) * this.retainedMotionFieldStride) + firstFieldColumn; |
|||
Array.Fill(this.retainedMotionField, entry, rowOffset, fieldWidth); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Projects one retained frame's motion field into the current frame's temporal candidate grid.
|
|||
/// </summary>
|
|||
/// <param name="sequenceHeader">The sequence header defining the modulo order-hint domain.</param>
|
|||
/// <param name="frameHeader">The current frame header.</param>
|
|||
/// <param name="startFrame">The retained frame whose stored motion vectors are projected.</param>
|
|||
/// <param name="reverseDirection">
|
|||
/// A value indicating whether the start-to-current distance and spatial displacement are reversed for a past frame.
|
|||
/// </param>
|
|||
/// <returns><see langword="true"/> when the retained frame is an eligible projection source.</returns>
|
|||
private bool ProjectMotionField( |
|||
ObuSequenceHeader sequenceHeader, |
|||
ObuFrameHeader frameHeader, |
|||
Av1ReferenceFrame startFrame, |
|||
bool reverseDirection) |
|||
{ |
|||
ObuFrameHeader startFrameHeader = startFrame.FrameHeader; |
|||
if (startFrameHeader.IsIntra || |
|||
startFrameHeader.ModeInfoRowCount != this.activeModeInfoRowCount || |
|||
startFrameHeader.ModeInfoColumnCount != this.activeModeInfoColumnCount) |
|||
{ |
|||
// AV1 does not rescale temporal fields. Intra sources contain no inter motion, and a differently sized
|
|||
// source has no one-to-one 8x8 grid on which the normative projection can operate.
|
|||
return false; |
|||
} |
|||
|
|||
Av1FrameInfo startFrameInfo = startFrame.FrameInfo; |
|||
int orderHintBits = sequenceHeader.OrderHintInfo.OrderHintBits; |
|||
int startToCurrentFrameOffset = GetRelativeDistance( |
|||
startFrameHeader.OrderHint, |
|||
frameHeader.OrderHint, |
|||
orderHintBits); |
|||
|
|||
if (reverseDirection) |
|||
{ |
|||
startToCurrentFrameOffset = -startToCurrentFrameOffset; |
|||
} |
|||
|
|||
int sourceRowCount = (this.activeModeInfoRowCount + 1) >> MotionFieldModeInfoShift; |
|||
int sourceColumnCount = (this.activeModeInfoColumnCount + 1) >> MotionFieldModeInfoShift; |
|||
int destinationRowCount = this.activeModeInfoRowCount >> MotionFieldModeInfoShift; |
|||
int destinationColumnCount = this.activeModeInfoColumnCount >> MotionFieldModeInfoShift; |
|||
|
|||
for (int blockRow = 0; blockRow < sourceRowCount; blockRow++) |
|||
{ |
|||
int sourceRowOffset = blockRow * startFrameInfo.retainedMotionFieldStride; |
|||
for (int blockColumn = 0; blockColumn < sourceColumnCount; blockColumn++) |
|||
{ |
|||
RetainedMotionFieldEntry source = startFrameInfo.retainedMotionField[sourceRowOffset + blockColumn]; |
|||
if (source.ReferenceFrame <= Av1ReferenceFrameType.Intra) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int referenceFrameOffset = GetRelativeDistance( |
|||
startFrameHeader.OrderHint, |
|||
startFrameInfo.motionFieldReferenceOrderHints[(int)source.ReferenceFrame], |
|||
orderHintBits); |
|||
|
|||
bool positionIsValid = Math.Abs(referenceFrameOffset) <= MaximumFrameDistance && |
|||
referenceFrameOffset > 0 && |
|||
Math.Abs(startToCurrentFrameOffset) <= MaximumFrameDistance; |
|||
|
|||
if (!positionIsValid) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
Av1MotionVector projected = ProjectMotionVector( |
|||
source.MotionVector, |
|||
startToCurrentFrameOffset, |
|||
referenceFrameOffset); |
|||
|
|||
if (!TryGetProjectedBlockPosition( |
|||
blockRow, |
|||
blockColumn, |
|||
projected, |
|||
reverseDirection, |
|||
destinationRowCount, |
|||
destinationColumnCount, |
|||
out int projectedRow, |
|||
out int projectedColumn)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
// The projected vector selects the destination cell, but AV1 stores the original forward vector and
|
|||
// its source-to-reference distance there. Candidate scaling later uses both values for its own target.
|
|||
int destinationOffset = (projectedRow * this.temporalMotionFieldStride) + projectedColumn; |
|||
this.temporalMotionField[destinationOffset] = new(source.MotionVector, referenceFrameOffset); |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales a retained motion vector by a signed ratio of temporal distances.
|
|||
/// </summary>
|
|||
/// <param name="motionVector">The retained vector in one-eighth-sample units.</param>
|
|||
/// <param name="numerator">The signed start-to-current temporal distance.</param>
|
|||
/// <param name="denominator">The positive start-to-reference temporal distance.</param>
|
|||
/// <returns>The projected and AV1-range-clamped vector.</returns>
|
|||
private static Av1MotionVector ProjectMotionVector(Av1MotionVector motionVector, int numerator, int denominator) |
|||
{ |
|||
denominator = Math.Min(denominator, MaximumFrameDistance); |
|||
numerator = Av1Math.Clip3(-MaximumFrameDistance, MaximumFrameDistance, numerator); |
|||
|
|||
// ProjectionDivisors represents 1 / denominator in Q14. Symmetric power-of-two rounding matches libaom for
|
|||
// negative vectors, and the final clamp excludes the two reserved extreme motion-vector values.
|
|||
int row = Av1Math.RoundPowerOf2Signed(motionVector.Row * numerator * ProjectionDivisors[denominator], 14); |
|||
int column = Av1Math.RoundPowerOf2Signed(motionVector.Column * numerator * ProjectionDivisors[denominator], 14); |
|||
row = Av1Math.Clip3(MotionVectorLowerBound + 1, MotionVectorUpperBound - 1, row); |
|||
column = Av1Math.Clip3(MotionVectorLowerBound + 1, MotionVectorUpperBound - 1, column); |
|||
return new(row, column); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps a projected motion vector to its bounded destination on the current 8x8 field.
|
|||
/// </summary>
|
|||
/// <param name="blockRow">The source 8x8 row.</param>
|
|||
/// <param name="blockColumn">The source 8x8 column.</param>
|
|||
/// <param name="motionVector">The temporally projected vector in one-eighth-sample units.</param>
|
|||
/// <param name="reverseDirection">Whether the vector moves backwards from the source position.</param>
|
|||
/// <param name="rowCount">The number of complete 8x8 rows in the current frame.</param>
|
|||
/// <param name="columnCount">The number of complete 8x8 columns in the current frame.</param>
|
|||
/// <param name="projectedRow">Receives the projected 8x8 row.</param>
|
|||
/// <param name="projectedColumn">Receives the projected 8x8 column.</param>
|
|||
/// <returns><see langword="true"/> when the destination lies in the permitted projection window.</returns>
|
|||
private static bool TryGetProjectedBlockPosition( |
|||
int blockRow, |
|||
int blockColumn, |
|||
Av1MotionVector motionVector, |
|||
bool reverseDirection, |
|||
int rowCount, |
|||
int columnCount, |
|||
out int projectedRow, |
|||
out int projectedColumn) |
|||
{ |
|||
int baseBlockRow = (blockRow >> 3) << 3; |
|||
int baseBlockColumn = (blockColumn >> 3) << 3; |
|||
|
|||
// One field cell spans 8 samples, while vectors use one-eighth-sample units; dividing by 64 converts between
|
|||
// them. C# integer division truncates toward zero, matching libaom's explicit signed-shift construction.
|
|||
int rowOffset = motionVector.Row / (1 << MotionVectorToFieldOffsetShift); |
|||
int columnOffset = motionVector.Column / (1 << MotionVectorToFieldOffsetShift); |
|||
projectedRow = reverseDirection ? blockRow - rowOffset : blockRow + rowOffset; |
|||
projectedColumn = reverseDirection ? blockColumn - columnOffset : blockColumn + columnOffset; |
|||
|
|||
if (projectedRow < 0 || projectedRow >= rowCount || projectedColumn < 0 || projectedColumn >= columnCount) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
// AV1 keeps a projection in the same 64x64 row band and permits one additional 64-sample horizontal band on
|
|||
// either side. This bounds temporal-candidate lookup while accommodating common lateral motion.
|
|||
return projectedRow >= baseBlockRow && |
|||
projectedRow < baseBlockRow + 8 && |
|||
projectedColumn >= baseBlockColumn - MaximumHorizontalFieldOffset && |
|||
projectedColumn < baseBlockColumn + 8 + MaximumHorizontalFieldOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the signed distance between two order hints in their modulo domain.
|
|||
/// </summary>
|
|||
/// <param name="first">The first order hint.</param>
|
|||
/// <param name="second">The order hint subtracted from <paramref name="first"/>.</param>
|
|||
/// <param name="orderHintBits">The number of bits in the order-hint domain.</param>
|
|||
/// <returns>The shortest signed modulo distance.</returns>
|
|||
private static int GetRelativeDistance(uint first, uint second, int orderHintBits) |
|||
{ |
|||
int difference = (int)first - (int)second; |
|||
int signBit = 1 << (orderHintBits - 1); |
|||
return (difference & (signBit - 1)) - (difference & signBit); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores one motion vector and logical reference retained for projection by a later frame.
|
|||
/// </summary>
|
|||
private readonly struct RetainedMotionFieldEntry |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RetainedMotionFieldEntry"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="motionVector">The retained motion vector in one-eighth-sample units.</param>
|
|||
/// <param name="referenceFrame">The logical reference targeted by the vector.</param>
|
|||
public RetainedMotionFieldEntry(Av1MotionVector motionVector, Av1ReferenceFrameType referenceFrame) |
|||
{ |
|||
this.MotionVector = motionVector; |
|||
this.ReferenceFrame = referenceFrame; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the retained motion vector in one-eighth-sample units.
|
|||
/// </summary>
|
|||
public Av1MotionVector MotionVector { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the logical reference targeted by <see cref="MotionVector"/>.
|
|||
/// </summary>
|
|||
public Av1ReferenceFrameType ReferenceFrame { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores one temporal candidate projected over the current frame's 8x8 grid.
|
|||
/// </summary>
|
|||
private readonly struct TemporalMotionFieldEntry |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TemporalMotionFieldEntry"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="motionVector">The retained source vector in one-eighth-sample units.</param>
|
|||
/// <param name="referenceFrameOffset">The positive temporal distance from the source to its reference.</param>
|
|||
public TemporalMotionFieldEntry(Av1MotionVector motionVector, int referenceFrameOffset) |
|||
{ |
|||
this.MotionVector = motionVector; |
|||
this.ReferenceFrameOffset = referenceFrameOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the retained source vector in one-eighth-sample units.
|
|||
/// </summary>
|
|||
public Av1MotionVector MotionVector { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the positive temporal distance from the source frame to its reference.
|
|||
/// </summary>
|
|||
public int ReferenceFrameOffset { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the intra predictor blended with a single-reference inter predictor.
|
|||
/// </summary>
|
|||
internal enum Av1InterIntraMode : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Uses a DC intra predictor.
|
|||
/// </summary>
|
|||
DC = 0, |
|||
|
|||
/// <summary>
|
|||
/// Uses a vertical intra predictor.
|
|||
/// </summary>
|
|||
Vertical = 1, |
|||
|
|||
/// <summary>
|
|||
/// Uses a horizontal intra predictor.
|
|||
/// </summary>
|
|||
Horizontal = 2, |
|||
|
|||
/// <summary>
|
|||
/// Uses a smooth intra predictor.
|
|||
/// </summary>
|
|||
Smooth = 3, |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the motion model used to construct an AV1 inter predictor.
|
|||
/// </summary>
|
|||
internal enum Av1MotionMode : byte |
|||
{ |
|||
/// <summary>
|
|||
/// Uses translational motion compensation without neighboring-block overlap.
|
|||
/// </summary>
|
|||
SimpleTranslation = 0, |
|||
|
|||
/// <summary>
|
|||
/// Blends the block with predictions derived from overlapping above and left neighbors.
|
|||
/// </summary>
|
|||
Obmc = 1, |
|||
|
|||
/// <summary>
|
|||
/// Uses a locally derived warped-motion model.
|
|||
/// </summary>
|
|||
Warped = 2, |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the current or retained frame used to predict an AV1 coding block.
|
|||
/// </summary>
|
|||
internal enum Av1ReferenceFrameType : sbyte |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates that the optional secondary reference is absent.
|
|||
/// </summary>
|
|||
None = -1, |
|||
|
|||
/// <summary>
|
|||
/// References the current frame for intra prediction.
|
|||
/// </summary>
|
|||
Intra = 0, |
|||
|
|||
/// <summary>
|
|||
/// References the most recent forward prediction frame.
|
|||
/// </summary>
|
|||
Last = 1, |
|||
|
|||
/// <summary>
|
|||
/// References the second most recent forward prediction frame.
|
|||
/// </summary>
|
|||
Last2 = 2, |
|||
|
|||
/// <summary>
|
|||
/// References the third most recent forward prediction frame.
|
|||
/// </summary>
|
|||
Last3 = 3, |
|||
|
|||
/// <summary>
|
|||
/// References the long-term golden forward prediction frame.
|
|||
/// </summary>
|
|||
Golden = 4, |
|||
|
|||
/// <summary>
|
|||
/// References the nearest backward prediction frame.
|
|||
/// </summary>
|
|||
Backward = 5, |
|||
|
|||
/// <summary>
|
|||
/// References the secondary alternate backward prediction frame.
|
|||
/// </summary>
|
|||
Alternate2 = 6, |
|||
|
|||
/// <summary>
|
|||
/// References the alternate backward prediction frame.
|
|||
/// </summary>
|
|||
Alternate = 7, |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point asymmetric discrete sine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Adst16Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point asymmetric discrete sine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Adst4Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst4(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point asymmetric discrete sine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Adst8Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst8(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point discrete cosine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Dct16Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the thirty-two-point discrete cosine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the thirty-two-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Dct32Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct32(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point discrete cosine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Dct4Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct4(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixty-four-point discrete cosine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sixty-four-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Dct64Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct64(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point discrete cosine forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Dct8Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct8(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point identity forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward identity transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Identity16Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Identity16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the thirty-two-point identity forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the thirty-two-point AV1 forward identity transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Identity32Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Identity32(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point identity forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 forward identity transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Identity4Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Identity4(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point identity forward transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 forward identity transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Identity8Operator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Identity8(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
} |
|||
@ -0,0 +1,574 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point asymmetric discrete sine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the 16-point AV1 inverse asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged rotations, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Adst16Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative 16-point AV1 inverse asymmetric discrete sine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The sixteen frequency-domain coefficients.</param>
|
|||
/// <param name="output">The sixteen spatial-domain residual values.</param>
|
|||
/// <param name="step">The sixteen-element stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output[0] = input[15]; |
|||
output[1] = input[0]; |
|||
output[2] = input[13]; |
|||
output[3] = input[2]; |
|||
output[4] = input[11]; |
|||
output[5] = input[4]; |
|||
output[6] = input[9]; |
|||
output[7] = input[6]; |
|||
output[8] = input[7]; |
|||
output[9] = input[8]; |
|||
output[10] = input[5]; |
|||
output[11] = input[10]; |
|||
output[12] = input[3]; |
|||
output[13] = input[12]; |
|||
output[14] = input[1]; |
|||
output[15] = input[14]; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step[0] = Av1Transform1dMath.HalfButterfly(cospi[2], output[0], cospi[62], output[1], cosBit); |
|||
step[1] = Av1Transform1dMath.HalfButterfly(cospi[62], output[0], -cospi[2], output[1], cosBit); |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[10], output[2], cospi[54], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[54], output[2], -cospi[10], output[3], cosBit); |
|||
step[4] = Av1Transform1dMath.HalfButterfly(cospi[18], output[4], cospi[46], output[5], cosBit); |
|||
step[5] = Av1Transform1dMath.HalfButterfly(cospi[46], output[4], -cospi[18], output[5], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[26], output[6], cospi[38], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[38], output[6], -cospi[26], output[7], cosBit); |
|||
step[8] = Av1Transform1dMath.HalfButterfly(cospi[34], output[8], cospi[30], output[9], cosBit); |
|||
step[9] = Av1Transform1dMath.HalfButterfly(cospi[30], output[8], -cospi[34], output[9], cosBit); |
|||
step[10] = Av1Transform1dMath.HalfButterfly(cospi[42], output[10], cospi[22], output[11], cosBit); |
|||
step[11] = Av1Transform1dMath.HalfButterfly(cospi[22], output[10], -cospi[42], output[11], cosBit); |
|||
step[12] = Av1Transform1dMath.HalfButterfly(cospi[50], output[12], cospi[14], output[13], cosBit); |
|||
step[13] = Av1Transform1dMath.HalfButterfly(cospi[14], output[12], -cospi[50], output[13], cosBit); |
|||
step[14] = Av1Transform1dMath.HalfButterfly(cospi[58], output[14], cospi[6], output[15], cosBit); |
|||
step[15] = Av1Transform1dMath.HalfButterfly(cospi[6], output[14], -cospi[58], output[15], cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two eight-sample halves and clamps each lane.
|
|||
stage++; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[8], stageRange[stage]); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[9], stageRange[stage]); |
|||
output[2] = Av1Transform1dMath.Clamp(step[2] + step[10], stageRange[stage]); |
|||
output[3] = Av1Transform1dMath.Clamp(step[3] + step[11], stageRange[stage]); |
|||
output[4] = Av1Transform1dMath.Clamp(step[4] + step[12], stageRange[stage]); |
|||
output[5] = Av1Transform1dMath.Clamp(step[5] + step[13], stageRange[stage]); |
|||
output[6] = Av1Transform1dMath.Clamp(step[6] + step[14], stageRange[stage]); |
|||
output[7] = Av1Transform1dMath.Clamp(step[7] + step[15], stageRange[stage]); |
|||
output[8] = Av1Transform1dMath.Clamp(step[0] - step[8], stageRange[stage]); |
|||
output[9] = Av1Transform1dMath.Clamp(step[1] - step[9], stageRange[stage]); |
|||
output[10] = Av1Transform1dMath.Clamp(step[2] - step[10], stageRange[stage]); |
|||
output[11] = Av1Transform1dMath.Clamp(step[3] - step[11], stageRange[stage]); |
|||
output[12] = Av1Transform1dMath.Clamp(step[4] - step[12], stageRange[stage]); |
|||
output[13] = Av1Transform1dMath.Clamp(step[5] - step[13], stageRange[stage]); |
|||
output[14] = Av1Transform1dMath.Clamp(step[6] - step[14], stageRange[stage]); |
|||
output[15] = Av1Transform1dMath.Clamp(step[7] - step[15], stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/16 rotations in the upper half.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = output[2]; |
|||
step[3] = output[3]; |
|||
step[4] = output[4]; |
|||
step[5] = output[5]; |
|||
step[6] = output[6]; |
|||
step[7] = output[7]; |
|||
step[8] = Av1Transform1dMath.HalfButterfly(cospi[8], output[8], cospi[56], output[9], cosBit); |
|||
step[9] = Av1Transform1dMath.HalfButterfly(cospi[56], output[8], -cospi[8], output[9], cosBit); |
|||
step[10] = Av1Transform1dMath.HalfButterfly(cospi[40], output[10], cospi[24], output[11], cosBit); |
|||
step[11] = Av1Transform1dMath.HalfButterfly(cospi[24], output[10], -cospi[40], output[11], cosBit); |
|||
step[12] = Av1Transform1dMath.HalfButterfly(-cospi[56], output[12], cospi[8], output[13], cosBit); |
|||
step[13] = Av1Transform1dMath.HalfButterfly(cospi[8], output[12], cospi[56], output[13], cosBit); |
|||
step[14] = Av1Transform1dMath.HalfButterfly(-cospi[24], output[14], cospi[40], output[15], cosBit); |
|||
step[15] = Av1Transform1dMath.HalfButterfly(cospi[40], output[14], cospi[24], output[15], cosBit); |
|||
|
|||
// Stage 5 separates each eight-sample half into four-sample groups and clamps each lane.
|
|||
stage++; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[4], stageRange[stage]); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[5], stageRange[stage]); |
|||
output[2] = Av1Transform1dMath.Clamp(step[2] + step[6], stageRange[stage]); |
|||
output[3] = Av1Transform1dMath.Clamp(step[3] + step[7], stageRange[stage]); |
|||
output[4] = Av1Transform1dMath.Clamp(step[0] - step[4], stageRange[stage]); |
|||
output[5] = Av1Transform1dMath.Clamp(step[1] - step[5], stageRange[stage]); |
|||
output[6] = Av1Transform1dMath.Clamp(step[2] - step[6], stageRange[stage]); |
|||
output[7] = Av1Transform1dMath.Clamp(step[3] - step[7], stageRange[stage]); |
|||
output[8] = Av1Transform1dMath.Clamp(step[8] + step[12], stageRange[stage]); |
|||
output[9] = Av1Transform1dMath.Clamp(step[9] + step[13], stageRange[stage]); |
|||
output[10] = Av1Transform1dMath.Clamp(step[10] + step[14], stageRange[stage]); |
|||
output[11] = Av1Transform1dMath.Clamp(step[11] + step[15], stageRange[stage]); |
|||
output[12] = Av1Transform1dMath.Clamp(step[8] - step[12], stageRange[stage]); |
|||
output[13] = Av1Transform1dMath.Clamp(step[9] - step[13], stageRange[stage]); |
|||
output[14] = Av1Transform1dMath.Clamp(step[10] - step[14], stageRange[stage]); |
|||
output[15] = Av1Transform1dMath.Clamp(step[11] - step[15], stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/8 and 3pi/8 rotations.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = output[2]; |
|||
step[3] = output[3]; |
|||
step[4] = Av1Transform1dMath.HalfButterfly(cospi[16], output[4], cospi[48], output[5], cosBit); |
|||
step[5] = Av1Transform1dMath.HalfButterfly(cospi[48], output[4], -cospi[16], output[5], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(-cospi[48], output[6], cospi[16], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[16], output[6], cospi[48], output[7], cosBit); |
|||
step[8] = output[8]; |
|||
step[9] = output[9]; |
|||
step[10] = output[10]; |
|||
step[11] = output[11]; |
|||
step[12] = Av1Transform1dMath.HalfButterfly(cospi[16], output[12], cospi[48], output[13], cosBit); |
|||
step[13] = Av1Transform1dMath.HalfButterfly(cospi[48], output[12], -cospi[16], output[13], cosBit); |
|||
step[14] = Av1Transform1dMath.HalfButterfly(-cospi[48], output[14], cospi[16], output[15], cosBit); |
|||
step[15] = Av1Transform1dMath.HalfButterfly(cospi[16], output[14], cospi[48], output[15], cosBit); |
|||
|
|||
// Stage 7 separates the four-sample groups into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[2], stageRange[stage]); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[3], stageRange[stage]); |
|||
output[2] = Av1Transform1dMath.Clamp(step[0] - step[2], stageRange[stage]); |
|||
output[3] = Av1Transform1dMath.Clamp(step[1] - step[3], stageRange[stage]); |
|||
output[4] = Av1Transform1dMath.Clamp(step[4] + step[6], stageRange[stage]); |
|||
output[5] = Av1Transform1dMath.Clamp(step[5] + step[7], stageRange[stage]); |
|||
output[6] = Av1Transform1dMath.Clamp(step[4] - step[6], stageRange[stage]); |
|||
output[7] = Av1Transform1dMath.Clamp(step[5] - step[7], stageRange[stage]); |
|||
output[8] = Av1Transform1dMath.Clamp(step[8] + step[10], stageRange[stage]); |
|||
output[9] = Av1Transform1dMath.Clamp(step[9] + step[11], stageRange[stage]); |
|||
output[10] = Av1Transform1dMath.Clamp(step[8] - step[10], stageRange[stage]); |
|||
output[11] = Av1Transform1dMath.Clamp(step[9] - step[11], stageRange[stage]); |
|||
output[12] = Av1Transform1dMath.Clamp(step[12] + step[14], stageRange[stage]); |
|||
output[13] = Av1Transform1dMath.Clamp(step[13] + step[15], stageRange[stage]); |
|||
output[14] = Av1Transform1dMath.Clamp(step[12] - step[14], stageRange[stage]); |
|||
output[15] = Av1Transform1dMath.Clamp(step[13] - step[15], stageRange[stage]); |
|||
|
|||
// Stage 8 reverses the pi/4 rotations for the middle pairs.
|
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[32], output[2], cospi[32], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[32], output[2], -cospi[32], output[3], cosBit); |
|||
step[4] = output[4]; |
|||
step[5] = output[5]; |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[32], output[6], cospi[32], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[32], output[6], -cospi[32], output[7], cosBit); |
|||
step[8] = output[8]; |
|||
step[9] = output[9]; |
|||
step[10] = Av1Transform1dMath.HalfButterfly(cospi[32], output[10], cospi[32], output[11], cosBit); |
|||
step[11] = Av1Transform1dMath.HalfButterfly(cospi[32], output[10], -cospi[32], output[11], cosBit); |
|||
step[12] = output[12]; |
|||
step[13] = output[13]; |
|||
step[14] = Av1Transform1dMath.HalfButterfly(cospi[32], output[14], cospi[32], output[15], cosBit); |
|||
step[15] = Av1Transform1dMath.HalfButterfly(cospi[32], output[14], -cospi[32], output[15], cosBit); |
|||
|
|||
// Stage 9 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output[0] = step[0]; |
|||
output[1] = -step[8]; |
|||
output[2] = step[12]; |
|||
output[3] = -step[4]; |
|||
output[4] = step[6]; |
|||
output[5] = -step[14]; |
|||
output[6] = step[10]; |
|||
output[7] = -step[2]; |
|||
output[8] = step[3]; |
|||
output[9] = -step[11]; |
|||
output[10] = step[15]; |
|||
output[11] = -step[7]; |
|||
output[12] = step[5]; |
|||
output[13] = -step[13]; |
|||
output[14] = step[9]; |
|||
output[15] = -step[1]; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output.V0 = input.V15; |
|||
output.V1 = input.V0; |
|||
output.V2 = input.V13; |
|||
output.V3 = input.V2; |
|||
output.V4 = input.V11; |
|||
output.V5 = input.V4; |
|||
output.V6 = input.V9; |
|||
output.V7 = input.V6; |
|||
output.V8 = input.V7; |
|||
output.V9 = input.V8; |
|||
output.V10 = input.V5; |
|||
output.V11 = input.V10; |
|||
output.V12 = input.V3; |
|||
output.V13 = input.V12; |
|||
output.V14 = input.V1; |
|||
output.V15 = input.V14; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[2], output.V0, cospi[62], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[62], output.V0, -cospi[2], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[10], output.V2, cospi[54], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[54], output.V2, -cospi[10], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[18], output.V4, cospi[46], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[46], output.V4, -cospi[18], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[26], output.V6, cospi[38], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[38], output.V6, -cospi[26], output.V7, cosBit); |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[34], output.V8, cospi[30], output.V9, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[30], output.V8, -cospi[34], output.V9, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[42], output.V10, cospi[22], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[22], output.V10, -cospi[42], output.V11, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[50], output.V12, cospi[14], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[14], output.V12, -cospi[50], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[58], output.V14, cospi[6], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[6], output.V14, -cospi[58], output.V15, cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two eight-sample halves and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V8, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V9, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V10, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V11, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V12, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V13, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V6 + step.V14, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V7 + step.V15, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V0 - step.V8, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V1 - step.V9, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V2 - step.V10, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V3 - step.V11, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V4 - step.V12, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V5 - step.V13, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V6 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V7 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/16 rotations in the upper half.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = output.V6; |
|||
step.V7 = output.V7; |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V8, cospi[56], output.V9, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[56], output.V8, -cospi[8], output.V9, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V10, cospi[24], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[24], output.V10, -cospi[40], output.V11, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(-cospi[56], output.V12, cospi[8], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V12, cospi[56], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(-cospi[24], output.V14, cospi[40], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V14, cospi[24], output.V15, cosBit); |
|||
|
|||
// Stage 5 separates each eight-sample half into four-sample groups and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V4, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V5, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V6, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V7, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V0 - step.V4, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V1 - step.V5, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V2 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V3 - step.V7, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V12, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V13, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V10 + step.V14, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V11 + step.V15, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V8 - step.V12, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V9 - step.V13, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V10 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V11 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/8 and 3pi/8 rotations.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V4, cospi[48], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V4, -cospi[16], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V6, cospi[16], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V6, cospi[48], output.V7, cosBit); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = output.V10; |
|||
step.V11 = output.V11; |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V12, cospi[48], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V12, -cospi[16], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V14, cospi[16], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V14, cospi[48], output.V15, cosBit); |
|||
|
|||
// Stage 7 separates the four-sample groups into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V2, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V3, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V0 - step.V2, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V1 - step.V3, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V6, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V7, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V4 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V5 - step.V7, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V10, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V11, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V8 - step.V10, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V9 - step.V11, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V12 + step.V14, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V13 + step.V15, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V12 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V13 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 8 reverses the pi/4 rotations for the middle pairs.
|
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, cospi[32], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, -cospi[32], output.V3, cosBit); |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, cospi[32], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, -cospi[32], output.V7, cosBit); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, cospi[32], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, -cospi[32], output.V11, cosBit); |
|||
step.V12 = output.V12; |
|||
step.V13 = output.V13; |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V14, cospi[32], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V14, -cospi[32], output.V15, cosBit); |
|||
|
|||
// Stage 9 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output.V0 = step.V0; |
|||
output.V1 = -step.V8; |
|||
output.V2 = step.V12; |
|||
output.V3 = -step.V4; |
|||
output.V4 = step.V6; |
|||
output.V5 = -step.V14; |
|||
output.V6 = step.V10; |
|||
output.V7 = -step.V2; |
|||
output.V8 = step.V3; |
|||
output.V9 = -step.V11; |
|||
output.V10 = step.V15; |
|||
output.V11 = -step.V7; |
|||
output.V12 = step.V5; |
|||
output.V13 = -step.V13; |
|||
output.V14 = step.V9; |
|||
output.V15 = -step.V1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the transform to four independent axes in parallel.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for the parallel transform axes.</param>
|
|||
/// <param name="output">The destination values for the parallel transform axes.</param>
|
|||
/// <param name="step">The fixed stage storage for the parallel transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output.V0 = input.V15; |
|||
output.V1 = input.V0; |
|||
output.V2 = input.V13; |
|||
output.V3 = input.V2; |
|||
output.V4 = input.V11; |
|||
output.V5 = input.V4; |
|||
output.V6 = input.V9; |
|||
output.V7 = input.V6; |
|||
output.V8 = input.V7; |
|||
output.V9 = input.V8; |
|||
output.V10 = input.V5; |
|||
output.V11 = input.V10; |
|||
output.V12 = input.V3; |
|||
output.V13 = input.V12; |
|||
output.V14 = input.V1; |
|||
output.V15 = input.V14; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[2], output.V0, cospi[62], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[62], output.V0, -cospi[2], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[10], output.V2, cospi[54], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[54], output.V2, -cospi[10], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[18], output.V4, cospi[46], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[46], output.V4, -cospi[18], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[26], output.V6, cospi[38], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[38], output.V6, -cospi[26], output.V7, cosBit); |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[34], output.V8, cospi[30], output.V9, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[30], output.V8, -cospi[34], output.V9, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[42], output.V10, cospi[22], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[22], output.V10, -cospi[42], output.V11, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[50], output.V12, cospi[14], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[14], output.V12, -cospi[50], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[58], output.V14, cospi[6], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[6], output.V14, -cospi[58], output.V15, cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two eight-sample halves and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V8, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V9, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V10, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V11, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V12, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V13, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V6 + step.V14, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V7 + step.V15, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V0 - step.V8, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V1 - step.V9, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V2 - step.V10, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V3 - step.V11, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V4 - step.V12, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V5 - step.V13, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V6 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V7 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/16 rotations in the upper half.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = output.V6; |
|||
step.V7 = output.V7; |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V8, cospi[56], output.V9, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[56], output.V8, -cospi[8], output.V9, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V10, cospi[24], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[24], output.V10, -cospi[40], output.V11, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(-cospi[56], output.V12, cospi[8], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V12, cospi[56], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(-cospi[24], output.V14, cospi[40], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V14, cospi[24], output.V15, cosBit); |
|||
|
|||
// Stage 5 separates each eight-sample half into four-sample groups and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V4, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V5, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V6, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V7, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V0 - step.V4, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V1 - step.V5, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V2 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V3 - step.V7, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V12, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V13, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V10 + step.V14, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V11 + step.V15, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V8 - step.V12, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V9 - step.V13, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V10 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V11 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/8 and 3pi/8 rotations.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V4, cospi[48], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V4, -cospi[16], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V6, cospi[16], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V6, cospi[48], output.V7, cosBit); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = output.V10; |
|||
step.V11 = output.V11; |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V12, cospi[48], output.V13, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V12, -cospi[16], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V14, cospi[16], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V14, cospi[48], output.V15, cosBit); |
|||
|
|||
// Stage 7 separates the four-sample groups into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V2, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V3, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V0 - step.V2, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V1 - step.V3, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V6, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V7, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V4 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V5 - step.V7, stageRange[stage]); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V10, stageRange[stage]); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V11, stageRange[stage]); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V8 - step.V10, stageRange[stage]); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V9 - step.V11, stageRange[stage]); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V12 + step.V14, stageRange[stage]); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V13 + step.V15, stageRange[stage]); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V12 - step.V14, stageRange[stage]); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V13 - step.V15, stageRange[stage]); |
|||
|
|||
// Stage 8 reverses the pi/4 rotations for the middle pairs.
|
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, cospi[32], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, -cospi[32], output.V3, cosBit); |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, cospi[32], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, -cospi[32], output.V7, cosBit); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, cospi[32], output.V11, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, -cospi[32], output.V11, cosBit); |
|||
step.V12 = output.V12; |
|||
step.V13 = output.V13; |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V14, cospi[32], output.V15, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V14, -cospi[32], output.V15, cosBit); |
|||
|
|||
// Stage 9 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output.V0 = step.V0; |
|||
output.V1 = -step.V8; |
|||
output.V2 = step.V12; |
|||
output.V3 = -step.V4; |
|||
output.V4 = step.V6; |
|||
output.V5 = -step.V14; |
|||
output.V6 = step.V10; |
|||
output.V7 = -step.V2; |
|||
output.V8 = step.V3; |
|||
output.V9 = -step.V11; |
|||
output.V10 = step.V15; |
|||
output.V11 = -step.V7; |
|||
output.V12 = step.V5; |
|||
output.V13 = -step.V13; |
|||
output.V14 = step.V9; |
|||
output.V15 = -step.V1; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point asymmetric discrete sine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 inverse asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged rotations, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Adst4Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative four-point AV1 inverse asymmetric discrete sine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The four frequency-domain coefficients.</param>
|
|||
/// <param name="output">The four spatial-domain residual values.</param>
|
|||
/// <param name="step">The stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the sine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> sinpi = Av1SinusConstants.SinusPi(cosBit); |
|||
|
|||
// libaom widens the complete four-point factorization because the products retain their fixed-point scale
|
|||
// until the final shift. The stage buffer is therefore unnecessary for this transform size.
|
|||
long x0 = input[0]; |
|||
long x1 = input[1]; |
|||
long x2 = input[2]; |
|||
long x3 = input[3]; |
|||
|
|||
_ = step; |
|||
_ = stageRange; |
|||
|
|||
// Avoid the multiplications for the all-zero coefficient vector, matching libaom's scalar kernel.
|
|||
if ((x0 | x1 | x2 | x3) == 0) |
|||
{ |
|||
output[..4].Clear(); |
|||
return; |
|||
} |
|||
|
|||
// Stages 1 and 2 form the seven sine products and the one unscaled combination used by stage 3.
|
|||
long s0 = sinpi[1] * x0; |
|||
long s1 = sinpi[2] * x0; |
|||
long s2 = sinpi[3] * x1; |
|||
long s3 = sinpi[4] * x2; |
|||
long s4 = sinpi[1] * x2; |
|||
long s5 = sinpi[2] * x3; |
|||
long s6 = sinpi[4] * x3; |
|||
long s7 = (x0 - x2) + x3; |
|||
|
|||
// Stages 3 through 6 combine the products while preserving the fixed-point scale until the final rounding.
|
|||
s0 += s3; |
|||
s1 -= s4; |
|||
s3 = s2; |
|||
s2 = sinpi[3] * s7; |
|||
s0 += s5; |
|||
s1 -= s6; |
|||
x0 = s0 + s3; |
|||
x1 = s1 + s3; |
|||
x2 = s2; |
|||
x3 = (s0 + s1) - s3; |
|||
|
|||
output[0] = Av1Math.RoundShift(x0, cosBit); |
|||
output[1] = Av1Math.RoundShift(x1, cosBit); |
|||
output[2] = Av1Math.RoundShift(x2, cosBit); |
|||
output[3] = Av1Math.RoundShift(x3, cosBit); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
TransformCore(ref input, ref output, cosBit); |
|||
_ = step; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
TransformCore(ref input, ref output, cosBit); |
|||
_ = step; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse four-point matrix to four independent axes.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for four transform axes.</param>
|
|||
/// <param name="output">The destination values for four transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the sine constants.</param>
|
|||
private static void TransformCore(ref Av1TransformVector<Vector128<int>> input, ref Av1TransformVector<Vector128<int>> output, int cosBit) |
|||
{ |
|||
ReadOnlySpan<int> sinpi = Av1SinusConstants.SinusPi(cosBit); |
|||
Vector128<int> x0 = input.V0; |
|||
Vector128<int> x1 = input.V1; |
|||
Vector128<int> x2 = input.V2; |
|||
Vector128<int> x3 = input.V3; |
|||
|
|||
// The products retain the sine-table scale across the complete matrix. The bounded transform inputs make
|
|||
// the optimized kernels' wrapping 32-bit multiply/add sequence valid until the terminal rounding shift.
|
|||
output.V0 = Av1Transform1dMath.MultiplyAdd4(sinpi[1], x0, sinpi[3], x1, sinpi[4], x2, sinpi[2], x3, cosBit); |
|||
output.V1 = Av1Transform1dMath.MultiplyAdd4(sinpi[2], x0, sinpi[3], x1, -sinpi[1], x2, -sinpi[4], x3, cosBit); |
|||
output.V2 = Av1Transform1dMath.MultiplyAdd4(sinpi[3], x0, 0, x1, -sinpi[3], x2, sinpi[3], x3, cosBit); |
|||
output.V3 = Av1Transform1dMath.MultiplyAdd4(sinpi[1] + sinpi[2], x0, -sinpi[3], x1, sinpi[4] - sinpi[1], x2, sinpi[2] - sinpi[4], x3, cosBit); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse four-point matrix to eight independent axes.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for eight transform axes.</param>
|
|||
/// <param name="output">The destination values for eight transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the sine constants.</param>
|
|||
private static void TransformCore(ref Av1TransformVector<Vector256<int>> input, ref Av1TransformVector<Vector256<int>> output, int cosBit) |
|||
{ |
|||
ReadOnlySpan<int> sinpi = Av1SinusConstants.SinusPi(cosBit); |
|||
Vector256<int> x0 = input.V0; |
|||
Vector256<int> x1 = input.V1; |
|||
Vector256<int> x2 = input.V2; |
|||
Vector256<int> x3 = input.V3; |
|||
|
|||
output.V0 = Av1Transform1dMath.MultiplyAdd4(sinpi[1], x0, sinpi[3], x1, sinpi[4], x2, sinpi[2], x3, cosBit); |
|||
output.V1 = Av1Transform1dMath.MultiplyAdd4(sinpi[2], x0, sinpi[3], x1, -sinpi[1], x2, -sinpi[4], x3, cosBit); |
|||
output.V2 = Av1Transform1dMath.MultiplyAdd4(sinpi[3], x0, 0, x1, -sinpi[3], x2, sinpi[3], x3, cosBit); |
|||
output.V3 = Av1Transform1dMath.MultiplyAdd4(sinpi[1] + sinpi[2], x0, -sinpi[3], x1, sinpi[4] - sinpi[1], x2, sinpi[2] - sinpi[4], x3, cosBit); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,295 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point asymmetric discrete sine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 inverse asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged rotations, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Adst8Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative eight-point AV1 inverse asymmetric discrete sine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The eight frequency-domain coefficients.</param>
|
|||
/// <param name="output">The eight spatial-domain residual values.</param>
|
|||
/// <param name="step">The eight-element stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output[0] = input[7]; |
|||
output[1] = input[0]; |
|||
output[2] = input[5]; |
|||
output[3] = input[2]; |
|||
output[4] = input[3]; |
|||
output[5] = input[4]; |
|||
output[6] = input[1]; |
|||
output[7] = input[6]; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step[0] = Av1Transform1dMath.HalfButterfly(cospi[4], output[0], cospi[60], output[1], cosBit); |
|||
step[1] = Av1Transform1dMath.HalfButterfly(cospi[60], output[0], -cospi[4], output[1], cosBit); |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[20], output[2], cospi[44], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[44], output[2], -cospi[20], output[3], cosBit); |
|||
step[4] = Av1Transform1dMath.HalfButterfly(cospi[36], output[4], cospi[28], output[5], cosBit); |
|||
step[5] = Av1Transform1dMath.HalfButterfly(cospi[28], output[4], -cospi[36], output[5], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[52], output[6], cospi[12], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[12], output[6], -cospi[52], output[7], cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two four-sample halves and clamps each lane.
|
|||
stage++; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[4], stageRange[stage]); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[5], stageRange[stage]); |
|||
output[2] = Av1Transform1dMath.Clamp(step[2] + step[6], stageRange[stage]); |
|||
output[3] = Av1Transform1dMath.Clamp(step[3] + step[7], stageRange[stage]); |
|||
output[4] = Av1Transform1dMath.Clamp(step[0] - step[4], stageRange[stage]); |
|||
output[5] = Av1Transform1dMath.Clamp(step[1] - step[5], stageRange[stage]); |
|||
output[6] = Av1Transform1dMath.Clamp(step[2] - step[6], stageRange[stage]); |
|||
output[7] = Av1Transform1dMath.Clamp(step[3] - step[7], stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/8 and 3pi/8 rotations in the upper half.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = output[2]; |
|||
step[3] = output[3]; |
|||
step[4] = Av1Transform1dMath.HalfButterfly(cospi[16], output[4], cospi[48], output[5], cosBit); |
|||
step[5] = Av1Transform1dMath.HalfButterfly(cospi[48], output[4], -cospi[16], output[5], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(-cospi[48], output[6], cospi[16], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[16], output[6], cospi[48], output[7], cosBit); |
|||
|
|||
// Stage 5 separates the four-sample halves into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[2], stageRange[stage]); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[3], stageRange[stage]); |
|||
output[2] = Av1Transform1dMath.Clamp(step[0] - step[2], stageRange[stage]); |
|||
output[3] = Av1Transform1dMath.Clamp(step[1] - step[3], stageRange[stage]); |
|||
output[4] = Av1Transform1dMath.Clamp(step[4] + step[6], stageRange[stage]); |
|||
output[5] = Av1Transform1dMath.Clamp(step[5] + step[7], stageRange[stage]); |
|||
output[6] = Av1Transform1dMath.Clamp(step[4] - step[6], stageRange[stage]); |
|||
output[7] = Av1Transform1dMath.Clamp(step[5] - step[7], stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/4 rotations for the middle pairs.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[32], output[2], cospi[32], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[32], output[2], -cospi[32], output[3], cosBit); |
|||
step[4] = output[4]; |
|||
step[5] = output[5]; |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[32], output[6], cospi[32], output[7], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[32], output[6], -cospi[32], output[7], cosBit); |
|||
|
|||
// Stage 7 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output[0] = step[0]; |
|||
output[1] = -step[4]; |
|||
output[2] = step[6]; |
|||
output[3] = -step[2]; |
|||
output[4] = step[3]; |
|||
output[5] = -step[7]; |
|||
output[6] = step[5]; |
|||
output[7] = -step[1]; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output.V0 = input.V7; |
|||
output.V1 = input.V0; |
|||
output.V2 = input.V5; |
|||
output.V3 = input.V2; |
|||
output.V4 = input.V3; |
|||
output.V5 = input.V4; |
|||
output.V6 = input.V1; |
|||
output.V7 = input.V6; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[4], output.V0, cospi[60], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[60], output.V0, -cospi[4], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[20], output.V2, cospi[44], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[44], output.V2, -cospi[20], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[36], output.V4, cospi[28], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[28], output.V4, -cospi[36], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[52], output.V6, cospi[12], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[12], output.V6, -cospi[52], output.V7, cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two four-sample halves and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V4, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V5, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V6, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V7, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V0 - step.V4, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V1 - step.V5, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V2 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V3 - step.V7, stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/8 and 3pi/8 rotations in the upper half.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V4, cospi[48], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V4, -cospi[16], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V6, cospi[16], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V6, cospi[48], output.V7, cosBit); |
|||
|
|||
// Stage 5 separates the four-sample halves into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V2, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V3, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V0 - step.V2, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V1 - step.V3, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V6, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V7, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V4 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V5 - step.V7, stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/4 rotations for the middle pairs.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, cospi[32], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, -cospi[32], output.V3, cosBit); |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, cospi[32], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, -cospi[32], output.V7, cosBit); |
|||
|
|||
// Stage 7 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output.V0 = step.V0; |
|||
output.V1 = -step.V4; |
|||
output.V2 = step.V6; |
|||
output.V3 = -step.V2; |
|||
output.V4 = step.V3; |
|||
output.V5 = -step.V7; |
|||
output.V6 = step.V5; |
|||
output.V7 = -step.V1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the transform to four independent axes in parallel.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for the parallel transform axes.</param>
|
|||
/// <param name="output">The destination values for the parallel transform axes.</param>
|
|||
/// <param name="step">The fixed stage storage for the parallel transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes the coefficients into the signed order used by the ADST factorization.
|
|||
stage++; |
|||
output.V0 = input.V7; |
|||
output.V1 = input.V0; |
|||
output.V2 = input.V5; |
|||
output.V3 = input.V2; |
|||
output.V4 = input.V3; |
|||
output.V5 = input.V4; |
|||
output.V6 = input.V1; |
|||
output.V7 = input.V6; |
|||
|
|||
// Stage 2 applies the terminal odd-angle rotations in reverse.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[4], output.V0, cospi[60], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[60], output.V0, -cospi[4], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[20], output.V2, cospi[44], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[44], output.V2, -cospi[20], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[36], output.V4, cospi[28], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[28], output.V4, -cospi[36], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[52], output.V6, cospi[12], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[12], output.V6, -cospi[52], output.V7, cosBit); |
|||
|
|||
// Stage 3 separates the complete butterfly into two four-sample halves and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V4, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V5, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V6, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V7, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V0 - step.V4, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V1 - step.V5, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V2 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V3 - step.V7, stageRange[stage]); |
|||
|
|||
// Stage 4 reverses the pi/8 and 3pi/8 rotations in the upper half.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V4, cospi[48], output.V5, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V4, -cospi[16], output.V5, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V6, cospi[16], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V6, cospi[48], output.V7, cosBit); |
|||
|
|||
// Stage 5 separates the four-sample halves into adjacent coefficient pairs and clamps each lane.
|
|||
stage++; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V2, stageRange[stage]); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V3, stageRange[stage]); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V0 - step.V2, stageRange[stage]); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V1 - step.V3, stageRange[stage]); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V6, stageRange[stage]); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V7, stageRange[stage]); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V4 - step.V6, stageRange[stage]); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V5 - step.V7, stageRange[stage]); |
|||
|
|||
// Stage 6 reverses the pi/4 rotations for the middle pairs.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, cospi[32], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V2, -cospi[32], output.V3, cosBit); |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, cospi[32], output.V7, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V6, -cospi[32], output.V7, cosBit); |
|||
|
|||
// Stage 7 applies the AV1 signs and permutation that restore spatial sample order.
|
|||
output.V0 = step.V0; |
|||
output.V1 = -step.V4; |
|||
output.V2 = step.V6; |
|||
output.V3 = -step.V2; |
|||
output.V4 = step.V3; |
|||
output.V5 = -step.V7; |
|||
output.V6 = step.V5; |
|||
output.V7 = -step.V1; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,481 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point discrete cosine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the 16-point AV1 inverse discrete cosine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged butterflies, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Dct16Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative 16-point AV1 inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The sixteen frequency-domain coefficients.</param>
|
|||
/// <param name="output">The sixteen spatial-domain residual values.</param>
|
|||
/// <param name="step">The sixteen-element stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output[0] = input[0]; |
|||
output[1] = input[8]; |
|||
output[2] = input[4]; |
|||
output[3] = input[12]; |
|||
output[4] = input[2]; |
|||
output[5] = input[10]; |
|||
output[6] = input[6]; |
|||
output[7] = input[14]; |
|||
output[8] = input[1]; |
|||
output[9] = input[9]; |
|||
output[10] = input[5]; |
|||
output[11] = input[13]; |
|||
output[12] = input[3]; |
|||
output[13] = input[11]; |
|||
output[14] = input[7]; |
|||
output[15] = input[15]; |
|||
|
|||
// Stage 2 rotates the highest odd-frequency coefficient pairs by their pi/32 angles.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = output[2]; |
|||
step[3] = output[3]; |
|||
step[4] = output[4]; |
|||
step[5] = output[5]; |
|||
step[6] = output[6]; |
|||
step[7] = output[7]; |
|||
step[8] = Av1Transform1dMath.HalfButterfly(cospi[60], output[8], -cospi[4], output[15], cosBit); |
|||
step[9] = Av1Transform1dMath.HalfButterfly(cospi[28], output[9], -cospi[36], output[14], cosBit); |
|||
step[10] = Av1Transform1dMath.HalfButterfly(cospi[44], output[10], -cospi[20], output[13], cosBit); |
|||
step[11] = Av1Transform1dMath.HalfButterfly(cospi[12], output[11], -cospi[52], output[12], cosBit); |
|||
step[12] = Av1Transform1dMath.HalfButterfly(cospi[52], output[11], cospi[12], output[12], cosBit); |
|||
step[13] = Av1Transform1dMath.HalfButterfly(cospi[20], output[10], cospi[44], output[13], cosBit); |
|||
step[14] = Av1Transform1dMath.HalfButterfly(cospi[36], output[9], cospi[28], output[14], cosBit); |
|||
step[15] = Av1Transform1dMath.HalfButterfly(cospi[4], output[8], cospi[60], output[15], cosBit); |
|||
|
|||
// Stage 3 reconstructs the embedded eight-point groups and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output[0] = step[0]; |
|||
output[1] = step[1]; |
|||
output[2] = step[2]; |
|||
output[3] = step[3]; |
|||
output[4] = Av1Transform1dMath.HalfButterfly(cospi[56], step[4], -cospi[8], step[7], cosBit); |
|||
output[5] = Av1Transform1dMath.HalfButterfly(cospi[24], step[5], -cospi[40], step[6], cosBit); |
|||
output[6] = Av1Transform1dMath.HalfButterfly(cospi[40], step[5], cospi[24], step[6], cosBit); |
|||
output[7] = Av1Transform1dMath.HalfButterfly(cospi[8], step[4], cospi[56], step[7], cosBit); |
|||
output[8] = Av1Transform1dMath.Clamp(step[8] + step[9], range); |
|||
output[9] = Av1Transform1dMath.Clamp(step[8] - step[9], range); |
|||
output[10] = Av1Transform1dMath.Clamp(step[11] - step[10], range); |
|||
output[11] = Av1Transform1dMath.Clamp(step[10] + step[11], range); |
|||
output[12] = Av1Transform1dMath.Clamp(step[12] + step[13], range); |
|||
output[13] = Av1Transform1dMath.Clamp(step[12] - step[13], range); |
|||
output[14] = Av1Transform1dMath.Clamp(step[15] - step[14], range); |
|||
output[15] = Av1Transform1dMath.Clamp(step[14] + step[15], range); |
|||
|
|||
// Stage 4 completes the low-frequency four-point DCT and rotates the next odd-frequency pairs.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step[0] = Av1Transform1dMath.HalfButterfly(cospi[32], output[0], cospi[32], output[1], cosBit); |
|||
step[1] = Av1Transform1dMath.HalfButterfly(cospi[32], output[0], -cospi[32], output[1], cosBit); |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[48], output[2], -cospi[16], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[16], output[2], cospi[48], output[3], cosBit); |
|||
step[4] = Av1Transform1dMath.Clamp(output[4] + output[5], range); |
|||
step[5] = Av1Transform1dMath.Clamp(output[4] - output[5], range); |
|||
step[6] = Av1Transform1dMath.Clamp(output[7] - output[6], range); |
|||
step[7] = Av1Transform1dMath.Clamp(output[6] + output[7], range); |
|||
step[8] = output[8]; |
|||
step[9] = Av1Transform1dMath.HalfButterfly(-cospi[16], output[9], cospi[48], output[14], cosBit); |
|||
step[10] = Av1Transform1dMath.HalfButterfly(-cospi[48], output[10], -cospi[16], output[13], cosBit); |
|||
step[11] = output[11]; |
|||
step[12] = output[12]; |
|||
step[13] = Av1Transform1dMath.HalfButterfly(-cospi[16], output[10], cospi[48], output[13], cosBit); |
|||
step[14] = Av1Transform1dMath.HalfButterfly(cospi[48], output[9], cospi[16], output[14], cosBit); |
|||
step[15] = output[15]; |
|||
|
|||
// Stage 5 widens the reconstructed groups through their next butterfly level.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[3], range); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[2], range); |
|||
output[2] = Av1Transform1dMath.Clamp(step[1] - step[2], range); |
|||
output[3] = Av1Transform1dMath.Clamp(step[0] - step[3], range); |
|||
output[4] = step[4]; |
|||
output[5] = Av1Transform1dMath.HalfButterfly(-cospi[32], step[5], cospi[32], step[6], cosBit); |
|||
output[6] = Av1Transform1dMath.HalfButterfly(cospi[32], step[5], cospi[32], step[6], cosBit); |
|||
output[7] = step[7]; |
|||
output[8] = Av1Transform1dMath.Clamp(step[8] + step[11], range); |
|||
output[9] = Av1Transform1dMath.Clamp(step[9] + step[10], range); |
|||
output[10] = Av1Transform1dMath.Clamp(step[9] - step[10], range); |
|||
output[11] = Av1Transform1dMath.Clamp(step[8] - step[11], range); |
|||
output[12] = Av1Transform1dMath.Clamp(step[15] - step[12], range); |
|||
output[13] = Av1Transform1dMath.Clamp(step[14] - step[13], range); |
|||
output[14] = Av1Transform1dMath.Clamp(step[13] + step[14], range); |
|||
output[15] = Av1Transform1dMath.Clamp(step[12] + step[15], range); |
|||
|
|||
// Stage 6 applies the remaining pi/4 rotations before the terminal spatial merge.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step[0] = Av1Transform1dMath.Clamp(output[0] + output[7], range); |
|||
step[1] = Av1Transform1dMath.Clamp(output[1] + output[6], range); |
|||
step[2] = Av1Transform1dMath.Clamp(output[2] + output[5], range); |
|||
step[3] = Av1Transform1dMath.Clamp(output[3] + output[4], range); |
|||
step[4] = Av1Transform1dMath.Clamp(output[3] - output[4], range); |
|||
step[5] = Av1Transform1dMath.Clamp(output[2] - output[5], range); |
|||
step[6] = Av1Transform1dMath.Clamp(output[1] - output[6], range); |
|||
step[7] = Av1Transform1dMath.Clamp(output[0] - output[7], range); |
|||
step[8] = output[8]; |
|||
step[9] = output[9]; |
|||
step[10] = Av1Transform1dMath.HalfButterfly(-cospi[32], output[10], cospi[32], output[13], cosBit); |
|||
step[11] = Av1Transform1dMath.HalfButterfly(-cospi[32], output[11], cospi[32], output[12], cosBit); |
|||
step[12] = Av1Transform1dMath.HalfButterfly(cospi[32], output[11], cospi[32], output[12], cosBit); |
|||
step[13] = Av1Transform1dMath.HalfButterfly(cospi[32], output[10], cospi[32], output[13], cosBit); |
|||
step[14] = output[14]; |
|||
step[15] = output[15]; |
|||
|
|||
// Stage 7 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[15], range); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[14], range); |
|||
output[2] = Av1Transform1dMath.Clamp(step[2] + step[13], range); |
|||
output[3] = Av1Transform1dMath.Clamp(step[3] + step[12], range); |
|||
output[4] = Av1Transform1dMath.Clamp(step[4] + step[11], range); |
|||
output[5] = Av1Transform1dMath.Clamp(step[5] + step[10], range); |
|||
output[6] = Av1Transform1dMath.Clamp(step[6] + step[9], range); |
|||
output[7] = Av1Transform1dMath.Clamp(step[7] + step[8], range); |
|||
output[8] = Av1Transform1dMath.Clamp(step[7] - step[8], range); |
|||
output[9] = Av1Transform1dMath.Clamp(step[6] - step[9], range); |
|||
output[10] = Av1Transform1dMath.Clamp(step[5] - step[10], range); |
|||
output[11] = Av1Transform1dMath.Clamp(step[4] - step[11], range); |
|||
output[12] = Av1Transform1dMath.Clamp(step[3] - step[12], range); |
|||
output[13] = Av1Transform1dMath.Clamp(step[2] - step[13], range); |
|||
output[14] = Av1Transform1dMath.Clamp(step[1] - step[14], range); |
|||
output[15] = Av1Transform1dMath.Clamp(step[0] - step[15], range); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output.V0 = input.V0; |
|||
output.V1 = input.V8; |
|||
output.V2 = input.V4; |
|||
output.V3 = input.V12; |
|||
output.V4 = input.V2; |
|||
output.V5 = input.V10; |
|||
output.V6 = input.V6; |
|||
output.V7 = input.V14; |
|||
output.V8 = input.V1; |
|||
output.V9 = input.V9; |
|||
output.V10 = input.V5; |
|||
output.V11 = input.V13; |
|||
output.V12 = input.V3; |
|||
output.V13 = input.V11; |
|||
output.V14 = input.V7; |
|||
output.V15 = input.V15; |
|||
|
|||
// Stage 2 rotates the highest odd-frequency coefficient pairs by their pi/32 angles.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = output.V6; |
|||
step.V7 = output.V7; |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[60], output.V8, -cospi[4], output.V15, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[28], output.V9, -cospi[36], output.V14, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[44], output.V10, -cospi[20], output.V13, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[12], output.V11, -cospi[52], output.V12, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[52], output.V11, cospi[12], output.V12, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[20], output.V10, cospi[44], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[36], output.V9, cospi[28], output.V14, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[4], output.V8, cospi[60], output.V15, cosBit); |
|||
|
|||
// Stage 3 reconstructs the embedded eight-point groups and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output.V0 = step.V0; |
|||
output.V1 = step.V1; |
|||
output.V2 = step.V2; |
|||
output.V3 = step.V3; |
|||
output.V4 = Av1Transform1dMath.HalfButterfly(cospi[56], step.V4, -cospi[8], step.V7, cosBit); |
|||
output.V5 = Av1Transform1dMath.HalfButterfly(cospi[24], step.V5, -cospi[40], step.V6, cosBit); |
|||
output.V6 = Av1Transform1dMath.HalfButterfly(cospi[40], step.V5, cospi[24], step.V6, cosBit); |
|||
output.V7 = Av1Transform1dMath.HalfButterfly(cospi[8], step.V4, cospi[56], step.V7, cosBit); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V9, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V8 - step.V9, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V11 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V10 + step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V12 + step.V13, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V12 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V15 - step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V14 + step.V15, range); |
|||
|
|||
// Stage 4 completes the low-frequency four-point DCT and rotates the next odd-frequency pairs.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, cospi[32], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, -cospi[32], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V2, -cospi[16], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V2, cospi[48], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.Clamp(output.V4 + output.V5, range); |
|||
step.V5 = Av1Transform1dMath.Clamp(output.V4 - output.V5, range); |
|||
step.V6 = Av1Transform1dMath.Clamp(output.V7 - output.V6, range); |
|||
step.V7 = Av1Transform1dMath.Clamp(output.V6 + output.V7, range); |
|||
step.V8 = output.V8; |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(-cospi[16], output.V9, cospi[48], output.V14, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V10, -cospi[16], output.V13, cosBit); |
|||
step.V11 = output.V11; |
|||
step.V12 = output.V12; |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(-cospi[16], output.V10, cospi[48], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V9, cospi[16], output.V14, cosBit); |
|||
step.V15 = output.V15; |
|||
|
|||
// Stage 5 widens the reconstructed groups through their next butterfly level.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V3, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V2, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V1 - step.V2, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V0 - step.V3, range); |
|||
output.V4 = step.V4; |
|||
output.V5 = Av1Transform1dMath.HalfButterfly(-cospi[32], step.V5, cospi[32], step.V6, cosBit); |
|||
output.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V5, cospi[32], step.V6, cosBit); |
|||
output.V7 = step.V7; |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V11, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V10, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V9 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V8 - step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V15 - step.V12, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V14 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V13 + step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V12 + step.V15, range); |
|||
|
|||
// Stage 6 applies the remaining pi/4 rotations before the terminal spatial merge.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step.V0 = Av1Transform1dMath.Clamp(output.V0 + output.V7, range); |
|||
step.V1 = Av1Transform1dMath.Clamp(output.V1 + output.V6, range); |
|||
step.V2 = Av1Transform1dMath.Clamp(output.V2 + output.V5, range); |
|||
step.V3 = Av1Transform1dMath.Clamp(output.V3 + output.V4, range); |
|||
step.V4 = Av1Transform1dMath.Clamp(output.V3 - output.V4, range); |
|||
step.V5 = Av1Transform1dMath.Clamp(output.V2 - output.V5, range); |
|||
step.V6 = Av1Transform1dMath.Clamp(output.V1 - output.V6, range); |
|||
step.V7 = Av1Transform1dMath.Clamp(output.V0 - output.V7, range); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V10, cospi[32], output.V13, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V11, cospi[32], output.V12, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V11, cospi[32], output.V12, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, cospi[32], output.V13, cosBit); |
|||
step.V14 = output.V14; |
|||
step.V15 = output.V15; |
|||
|
|||
// Stage 7 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V15, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V14, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V13, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V12, range); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V11, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V10, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V6 + step.V9, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V7 + step.V8, range); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V7 - step.V8, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V6 - step.V9, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V5 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V4 - step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V3 - step.V12, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V2 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V1 - step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V0 - step.V15, range); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the transform to four independent axes in parallel.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for the parallel transform axes.</param>
|
|||
/// <param name="output">The destination values for the parallel transform axes.</param>
|
|||
/// <param name="step">The fixed stage storage for the parallel transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output.V0 = input.V0; |
|||
output.V1 = input.V8; |
|||
output.V2 = input.V4; |
|||
output.V3 = input.V12; |
|||
output.V4 = input.V2; |
|||
output.V5 = input.V10; |
|||
output.V6 = input.V6; |
|||
output.V7 = input.V14; |
|||
output.V8 = input.V1; |
|||
output.V9 = input.V9; |
|||
output.V10 = input.V5; |
|||
output.V11 = input.V13; |
|||
output.V12 = input.V3; |
|||
output.V13 = input.V11; |
|||
output.V14 = input.V7; |
|||
output.V15 = input.V15; |
|||
|
|||
// Stage 2 rotates the highest odd-frequency coefficient pairs by their pi/32 angles.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = output.V4; |
|||
step.V5 = output.V5; |
|||
step.V6 = output.V6; |
|||
step.V7 = output.V7; |
|||
step.V8 = Av1Transform1dMath.HalfButterfly(cospi[60], output.V8, -cospi[4], output.V15, cosBit); |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(cospi[28], output.V9, -cospi[36], output.V14, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(cospi[44], output.V10, -cospi[20], output.V13, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(cospi[12], output.V11, -cospi[52], output.V12, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[52], output.V11, cospi[12], output.V12, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[20], output.V10, cospi[44], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[36], output.V9, cospi[28], output.V14, cosBit); |
|||
step.V15 = Av1Transform1dMath.HalfButterfly(cospi[4], output.V8, cospi[60], output.V15, cosBit); |
|||
|
|||
// Stage 3 reconstructs the embedded eight-point groups and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output.V0 = step.V0; |
|||
output.V1 = step.V1; |
|||
output.V2 = step.V2; |
|||
output.V3 = step.V3; |
|||
output.V4 = Av1Transform1dMath.HalfButterfly(cospi[56], step.V4, -cospi[8], step.V7, cosBit); |
|||
output.V5 = Av1Transform1dMath.HalfButterfly(cospi[24], step.V5, -cospi[40], step.V6, cosBit); |
|||
output.V6 = Av1Transform1dMath.HalfButterfly(cospi[40], step.V5, cospi[24], step.V6, cosBit); |
|||
output.V7 = Av1Transform1dMath.HalfButterfly(cospi[8], step.V4, cospi[56], step.V7, cosBit); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V9, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V8 - step.V9, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V11 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V10 + step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V12 + step.V13, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V12 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V15 - step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V14 + step.V15, range); |
|||
|
|||
// Stage 4 completes the low-frequency four-point DCT and rotates the next odd-frequency pairs.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, cospi[32], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, -cospi[32], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V2, -cospi[16], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V2, cospi[48], output.V3, cosBit); |
|||
step.V4 = Av1Transform1dMath.Clamp(output.V4 + output.V5, range); |
|||
step.V5 = Av1Transform1dMath.Clamp(output.V4 - output.V5, range); |
|||
step.V6 = Av1Transform1dMath.Clamp(output.V7 - output.V6, range); |
|||
step.V7 = Av1Transform1dMath.Clamp(output.V6 + output.V7, range); |
|||
step.V8 = output.V8; |
|||
step.V9 = Av1Transform1dMath.HalfButterfly(-cospi[16], output.V9, cospi[48], output.V14, cosBit); |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(-cospi[48], output.V10, -cospi[16], output.V13, cosBit); |
|||
step.V11 = output.V11; |
|||
step.V12 = output.V12; |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(-cospi[16], output.V10, cospi[48], output.V13, cosBit); |
|||
step.V14 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V9, cospi[16], output.V14, cosBit); |
|||
step.V15 = output.V15; |
|||
|
|||
// Stage 5 widens the reconstructed groups through their next butterfly level.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V3, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V2, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V1 - step.V2, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V0 - step.V3, range); |
|||
output.V4 = step.V4; |
|||
output.V5 = Av1Transform1dMath.HalfButterfly(-cospi[32], step.V5, cospi[32], step.V6, cosBit); |
|||
output.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V5, cospi[32], step.V6, cosBit); |
|||
output.V7 = step.V7; |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V8 + step.V11, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V9 + step.V10, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V9 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V8 - step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V15 - step.V12, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V14 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V13 + step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V12 + step.V15, range); |
|||
|
|||
// Stage 6 applies the remaining pi/4 rotations before the terminal spatial merge.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
step.V0 = Av1Transform1dMath.Clamp(output.V0 + output.V7, range); |
|||
step.V1 = Av1Transform1dMath.Clamp(output.V1 + output.V6, range); |
|||
step.V2 = Av1Transform1dMath.Clamp(output.V2 + output.V5, range); |
|||
step.V3 = Av1Transform1dMath.Clamp(output.V3 + output.V4, range); |
|||
step.V4 = Av1Transform1dMath.Clamp(output.V3 - output.V4, range); |
|||
step.V5 = Av1Transform1dMath.Clamp(output.V2 - output.V5, range); |
|||
step.V6 = Av1Transform1dMath.Clamp(output.V1 - output.V6, range); |
|||
step.V7 = Av1Transform1dMath.Clamp(output.V0 - output.V7, range); |
|||
step.V8 = output.V8; |
|||
step.V9 = output.V9; |
|||
step.V10 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V10, cospi[32], output.V13, cosBit); |
|||
step.V11 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V11, cospi[32], output.V12, cosBit); |
|||
step.V12 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V11, cospi[32], output.V12, cosBit); |
|||
step.V13 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V10, cospi[32], output.V13, cosBit); |
|||
step.V14 = output.V14; |
|||
step.V15 = output.V15; |
|||
|
|||
// Stage 7 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V15, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V14, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V13, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V12, range); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V11, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V5 + step.V10, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V6 + step.V9, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V7 + step.V8, range); |
|||
output.V8 = Av1Transform1dMath.Clamp(step.V7 - step.V8, range); |
|||
output.V9 = Av1Transform1dMath.Clamp(step.V6 - step.V9, range); |
|||
output.V10 = Av1Transform1dMath.Clamp(step.V5 - step.V10, range); |
|||
output.V11 = Av1Transform1dMath.Clamp(step.V4 - step.V11, range); |
|||
output.V12 = Av1Transform1dMath.Clamp(step.V3 - step.V12, range); |
|||
output.V13 = Av1Transform1dMath.Clamp(step.V2 - step.V13, range); |
|||
output.V14 = Av1Transform1dMath.Clamp(step.V1 - step.V14, range); |
|||
output.V15 = Av1Transform1dMath.Clamp(step.V0 - step.V15, range); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,118 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point discrete cosine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 inverse discrete cosine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged butterflies, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Dct4Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative four-point AV1 inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The four frequency-domain coefficients.</param>
|
|||
/// <param name="output">The four spatial-domain residual values.</param>
|
|||
/// <param name="step">The four-element stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
// AV1 stores coefficients in frequency order; this permutation restores the order expected by the staged DCT.
|
|||
output[0] = input[0]; |
|||
output[1] = input[2]; |
|||
output[2] = input[1]; |
|||
output[3] = input[3]; |
|||
|
|||
// Rotate the even and odd coefficient pairs using the same fixed-point basis as the forward transform.
|
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
step[0] = Av1Transform1dMath.HalfButterfly(cospi[32], output[0], cospi[32], output[1], cosBit); |
|||
step[1] = Av1Transform1dMath.HalfButterfly(cospi[32], output[0], -cospi[32], output[1], cosBit); |
|||
step[2] = Av1Transform1dMath.HalfButterfly(cospi[48], output[2], -cospi[16], output[3], cosBit); |
|||
step[3] = Av1Transform1dMath.HalfButterfly(cospi[16], output[2], cospi[48], output[3], cosBit); |
|||
|
|||
// The terminal butterflies reconstruct spatial order and clamp every result to the normative stage range.
|
|||
byte range = stageRange[3]; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[3], range); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[2], range); |
|||
output[2] = Av1Transform1dMath.Clamp(step[1] - step[2], range); |
|||
output[3] = Av1Transform1dMath.Clamp(step[0] - step[3], range); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
// AV1 stores coefficients in frequency order; this permutation restores the order expected by the staged DCT.
|
|||
output.V0 = input.V0; |
|||
output.V1 = input.V2; |
|||
output.V2 = input.V1; |
|||
output.V3 = input.V3; |
|||
|
|||
// Rotate the even and odd coefficient pairs using the same fixed-point basis as the forward transform.
|
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, cospi[32], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, -cospi[32], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V2, -cospi[16], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V2, cospi[48], output.V3, cosBit); |
|||
|
|||
// The terminal butterflies reconstruct spatial order and clamp every result to the normative stage range.
|
|||
byte range = stageRange[3]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V3, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V2, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V1 - step.V2, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V0 - step.V3, range); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the transform to four independent axes in parallel.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for the parallel transform axes.</param>
|
|||
/// <param name="output">The destination values for the parallel transform axes.</param>
|
|||
/// <param name="step">The fixed stage storage for the parallel transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
// AV1 stores coefficients in frequency order; this permutation restores the order expected by the staged DCT.
|
|||
output.V0 = input.V0; |
|||
output.V1 = input.V2; |
|||
output.V2 = input.V1; |
|||
output.V3 = input.V3; |
|||
|
|||
// Rotate the even and odd coefficient pairs using the same fixed-point basis as the forward transform.
|
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
step.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, cospi[32], output.V1, cosBit); |
|||
step.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V0, -cospi[32], output.V1, cosBit); |
|||
step.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], output.V2, -cospi[16], output.V3, cosBit); |
|||
step.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], output.V2, cospi[48], output.V3, cosBit); |
|||
|
|||
// The terminal butterflies reconstruct spatial order and clamp every result to the normative stage range.
|
|||
byte range = stageRange[3]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V3, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V2, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V1 - step.V2, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V0 - step.V3, range); |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,238 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point discrete cosine inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 inverse discrete cosine transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. The SIMD overloads apply
|
|||
/// the same staged butterflies, fixed-point rounding, and range clamps as the scalar overload without mixing axes.
|
|||
/// </remarks>
|
|||
internal readonly struct Dct8Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative eight-point AV1 inverse discrete cosine transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The eight frequency-domain coefficients.</param>
|
|||
/// <param name="output">The eight spatial-domain residual values.</param>
|
|||
/// <param name="step">The eight-element stage buffer owned by the containing two-dimensional transform.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output[0] = input[0]; |
|||
output[1] = input[4]; |
|||
output[2] = input[2]; |
|||
output[3] = input[6]; |
|||
output[4] = input[1]; |
|||
output[5] = input[5]; |
|||
output[6] = input[3]; |
|||
output[7] = input[7]; |
|||
|
|||
// Stage 2 rotates the odd-frequency coefficient pairs by their pi/16 angles.
|
|||
stage++; |
|||
step[0] = output[0]; |
|||
step[1] = output[1]; |
|||
step[2] = output[2]; |
|||
step[3] = output[3]; |
|||
step[4] = Av1Transform1dMath.HalfButterfly(cospi[56], output[4], -cospi[8], output[7], cosBit); |
|||
step[5] = Av1Transform1dMath.HalfButterfly(cospi[24], output[5], -cospi[40], output[6], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[40], output[5], cospi[24], output[6], cosBit); |
|||
step[7] = Av1Transform1dMath.HalfButterfly(cospi[8], output[4], cospi[56], output[7], cosBit); |
|||
|
|||
// Stage 3 reconstructs the even four-point DCT and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output[0] = Av1Transform1dMath.HalfButterfly(cospi[32], step[0], cospi[32], step[1], cosBit); |
|||
output[1] = Av1Transform1dMath.HalfButterfly(cospi[32], step[0], -cospi[32], step[1], cosBit); |
|||
output[2] = Av1Transform1dMath.HalfButterfly(cospi[48], step[2], -cospi[16], step[3], cosBit); |
|||
output[3] = Av1Transform1dMath.HalfButterfly(cospi[16], step[2], cospi[48], step[3], cosBit); |
|||
output[4] = Av1Transform1dMath.Clamp(step[4] + step[5], range); |
|||
output[5] = Av1Transform1dMath.Clamp(step[4] - step[5], range); |
|||
output[6] = Av1Transform1dMath.Clamp(step[7] - step[6], range); |
|||
output[7] = Av1Transform1dMath.Clamp(step[6] + step[7], range); |
|||
|
|||
// Stage 4 completes the even butterflies and applies the remaining pi/4 odd rotation.
|
|||
stage++; |
|||
step[0] = Av1Transform1dMath.Clamp(output[0] + output[3], range); |
|||
step[1] = Av1Transform1dMath.Clamp(output[1] + output[2], range); |
|||
step[2] = Av1Transform1dMath.Clamp(output[1] - output[2], range); |
|||
step[3] = Av1Transform1dMath.Clamp(output[0] - output[3], range); |
|||
step[4] = output[4]; |
|||
step[5] = Av1Transform1dMath.HalfButterfly(-cospi[32], output[5], cospi[32], output[6], cosBit); |
|||
step[6] = Av1Transform1dMath.HalfButterfly(cospi[32], output[5], cospi[32], output[6], cosBit); |
|||
step[7] = output[7]; |
|||
|
|||
// Stage 5 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output[0] = Av1Transform1dMath.Clamp(step[0] + step[7], range); |
|||
output[1] = Av1Transform1dMath.Clamp(step[1] + step[6], range); |
|||
output[2] = Av1Transform1dMath.Clamp(step[2] + step[5], range); |
|||
output[3] = Av1Transform1dMath.Clamp(step[3] + step[4], range); |
|||
output[4] = Av1Transform1dMath.Clamp(step[3] - step[4], range); |
|||
output[5] = Av1Transform1dMath.Clamp(step[2] - step[5], range); |
|||
output[6] = Av1Transform1dMath.Clamp(step[1] - step[6], range); |
|||
output[7] = Av1Transform1dMath.Clamp(step[0] - step[7], range); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output.V0 = input.V0; |
|||
output.V1 = input.V4; |
|||
output.V2 = input.V2; |
|||
output.V3 = input.V6; |
|||
output.V4 = input.V1; |
|||
output.V5 = input.V5; |
|||
output.V6 = input.V3; |
|||
output.V7 = input.V7; |
|||
|
|||
// Stage 2 rotates the odd-frequency coefficient pairs by their pi/16 angles.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[56], output.V4, -cospi[8], output.V7, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[24], output.V5, -cospi[40], output.V6, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V5, cospi[24], output.V6, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V4, cospi[56], output.V7, cosBit); |
|||
|
|||
// Stage 3 reconstructs the even four-point DCT and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V0, cospi[32], step.V1, cosBit); |
|||
output.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V0, -cospi[32], step.V1, cosBit); |
|||
output.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], step.V2, -cospi[16], step.V3, cosBit); |
|||
output.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], step.V2, cospi[48], step.V3, cosBit); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V5, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V4 - step.V5, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V7 - step.V6, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V6 + step.V7, range); |
|||
|
|||
// Stage 4 completes the even butterflies and applies the remaining pi/4 odd rotation.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.Clamp(output.V0 + output.V3, range); |
|||
step.V1 = Av1Transform1dMath.Clamp(output.V1 + output.V2, range); |
|||
step.V2 = Av1Transform1dMath.Clamp(output.V1 - output.V2, range); |
|||
step.V3 = Av1Transform1dMath.Clamp(output.V0 - output.V3, range); |
|||
step.V4 = output.V4; |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V5, cospi[32], output.V6, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V5, cospi[32], output.V6, cosBit); |
|||
step.V7 = output.V7; |
|||
|
|||
// Stage 5 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V7, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V6, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V5, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V4, range); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V3 - step.V4, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V2 - step.V5, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V1 - step.V6, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V0 - step.V7, range); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the transform to four independent axes in parallel.
|
|||
/// </summary>
|
|||
/// <param name="input">The source values for the parallel transform axes.</param>
|
|||
/// <param name="output">The destination values for the parallel transform axes.</param>
|
|||
/// <param name="step">The fixed stage storage for the parallel transform axes.</param>
|
|||
/// <param name="cosBit">The fixed-point precision of the cosine constants.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to each transform stage.</param>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
ReadOnlySpan<int> cospi = Av1SinusConstants.CosinusPi(cosBit); |
|||
int stage = 0; |
|||
|
|||
// Stage 1 permutes frequency-ordered coefficients into the recursive DCT factorization order.
|
|||
stage++; |
|||
output.V0 = input.V0; |
|||
output.V1 = input.V4; |
|||
output.V2 = input.V2; |
|||
output.V3 = input.V6; |
|||
output.V4 = input.V1; |
|||
output.V5 = input.V5; |
|||
output.V6 = input.V3; |
|||
output.V7 = input.V7; |
|||
|
|||
// Stage 2 rotates the odd-frequency coefficient pairs by their pi/16 angles.
|
|||
stage++; |
|||
step.V0 = output.V0; |
|||
step.V1 = output.V1; |
|||
step.V2 = output.V2; |
|||
step.V3 = output.V3; |
|||
step.V4 = Av1Transform1dMath.HalfButterfly(cospi[56], output.V4, -cospi[8], output.V7, cosBit); |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(cospi[24], output.V5, -cospi[40], output.V6, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[40], output.V5, cospi[24], output.V6, cosBit); |
|||
step.V7 = Av1Transform1dMath.HalfButterfly(cospi[8], output.V4, cospi[56], output.V7, cosBit); |
|||
|
|||
// Stage 3 reconstructs the even four-point DCT and combines adjacent odd terms.
|
|||
stage++; |
|||
byte range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V0, cospi[32], step.V1, cosBit); |
|||
output.V1 = Av1Transform1dMath.HalfButterfly(cospi[32], step.V0, -cospi[32], step.V1, cosBit); |
|||
output.V2 = Av1Transform1dMath.HalfButterfly(cospi[48], step.V2, -cospi[16], step.V3, cosBit); |
|||
output.V3 = Av1Transform1dMath.HalfButterfly(cospi[16], step.V2, cospi[48], step.V3, cosBit); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V4 + step.V5, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V4 - step.V5, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V7 - step.V6, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V6 + step.V7, range); |
|||
|
|||
// Stage 4 completes the even butterflies and applies the remaining pi/4 odd rotation.
|
|||
stage++; |
|||
step.V0 = Av1Transform1dMath.Clamp(output.V0 + output.V3, range); |
|||
step.V1 = Av1Transform1dMath.Clamp(output.V1 + output.V2, range); |
|||
step.V2 = Av1Transform1dMath.Clamp(output.V1 - output.V2, range); |
|||
step.V3 = Av1Transform1dMath.Clamp(output.V0 - output.V3, range); |
|||
step.V4 = output.V4; |
|||
step.V5 = Av1Transform1dMath.HalfButterfly(-cospi[32], output.V5, cospi[32], output.V6, cosBit); |
|||
step.V6 = Av1Transform1dMath.HalfButterfly(cospi[32], output.V5, cospi[32], output.V6, cosBit); |
|||
step.V7 = output.V7; |
|||
|
|||
// Stage 5 merges the even and odd halves into spatial order and clamps every result.
|
|||
stage++; |
|||
range = stageRange[stage]; |
|||
output.V0 = Av1Transform1dMath.Clamp(step.V0 + step.V7, range); |
|||
output.V1 = Av1Transform1dMath.Clamp(step.V1 + step.V6, range); |
|||
output.V2 = Av1Transform1dMath.Clamp(step.V2 + step.V5, range); |
|||
output.V3 = Av1Transform1dMath.Clamp(step.V3 + step.V4, range); |
|||
output.V4 = Av1Transform1dMath.Clamp(step.V3 - step.V4, range); |
|||
output.V5 = Av1Transform1dMath.Clamp(step.V2 - step.V5, range); |
|||
output.V6 = Av1Transform1dMath.Clamp(step.V1 - step.V6, range); |
|||
output.V7 = Av1Transform1dMath.Clamp(step.V0 - step.V7, range); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sixteen-point identity inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 inverse identity transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. Scaling is lane-local,
|
|||
/// so the SIMD overloads preserve the scalar fixed-point multiplier and rounding for every axis.
|
|||
/// </remarks>
|
|||
internal readonly struct Identity16Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative sixteen-point AV1 inverse identity transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The sixteen frequency-domain coefficients.</param>
|
|||
/// <param name="output">The sixteen scaled spatial-domain values.</param>
|
|||
/// <param name="step">Unused stage storage supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="cosBit">Unused cosine precision supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to the transform output.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
|
|||
// The AV1 identity transform preserves coefficient order while applying the twice the square-root-of-two fixed-point scale required for 2-D normalization.
|
|||
for (int i = 0; i < 16; i++) |
|||
{ |
|||
output[i] = Av1Math.RoundShift((long)input[i] * (2 * Av1Transform1dMath.NewSqrt2), Av1Transform1dMath.NewSqrt2Bits); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 16, 2 * Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 16, 2 * Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the thirty-two-point identity inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the thirty-two-point AV1 inverse identity transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. Scaling is lane-local,
|
|||
/// so the SIMD overloads preserve the scalar fixed-point multiplier and rounding for every axis.
|
|||
/// </remarks>
|
|||
internal readonly struct Identity32Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative thirty-two-point AV1 inverse identity transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The thirty-two frequency-domain coefficients.</param>
|
|||
/// <param name="output">The thirty-two scaled spatial-domain values.</param>
|
|||
/// <param name="step">Unused stage storage supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="cosBit">Unused cosine precision supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to the transform output.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
|
|||
// The AV1 identity transform preserves coefficient order while applying the exact factor-of-four scale required for 2-D normalization.
|
|||
for (int i = 0; i < 32; i++) |
|||
{ |
|||
output[i] = input[i] * 4; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 32, 4, 0); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 32, 4, 0); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the four-point identity inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the four-point AV1 inverse identity transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. Scaling is lane-local,
|
|||
/// so the SIMD overloads preserve the scalar fixed-point multiplier and rounding for every axis.
|
|||
/// </remarks>
|
|||
internal readonly struct Identity4Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative four-point AV1 inverse identity transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The four frequency-domain coefficients.</param>
|
|||
/// <param name="output">The four scaled spatial-domain values.</param>
|
|||
/// <param name="step">Unused stage storage supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="cosBit">Unused cosine precision supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to the transform output.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
|
|||
// The AV1 identity transform preserves coefficient order while applying the square-root-of-two fixed-point scale required for 2-D normalization.
|
|||
for (int i = 0; i < 4; i++) |
|||
{ |
|||
output[i] = Av1Math.RoundShift((long)input[i] * Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 4, Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 4, Av1Transform1dMath.NewSqrt2, Av1Transform1dMath.NewSqrt2Bits); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the eight-point identity inverse transform operator.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 inverse identity transform operator.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Vector fields represent transform positions and vector lanes represent independent axes. Scaling is lane-local,
|
|||
/// so the SIMD overloads preserve the scalar fixed-point multiplier and rounding for every axis.
|
|||
/// </remarks>
|
|||
internal readonly struct Identity8Operator : IAv1InverseTransform1dOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Applies the normative eight-point AV1 inverse identity transform.
|
|||
/// </summary>
|
|||
/// <param name="input">The eight frequency-domain coefficients.</param>
|
|||
/// <param name="output">The eight scaled spatial-domain values.</param>
|
|||
/// <param name="step">Unused stage storage supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="cosBit">Unused cosine precision supplied by the common transform-kernel contract.</param>
|
|||
/// <param name="stageRange">The signed-bit range assigned to the transform output.</param>
|
|||
public static void Transform(ReadOnlySpan<int> input, Span<int> output, Span<int> step, int cosBit, Av1TransformStageRange stageRange) |
|||
{ |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
|
|||
// The AV1 identity transform preserves coefficient order while applying the exact factor-of-two scale required for 2-D normalization.
|
|||
for (int i = 0; i < 8; i++) |
|||
{ |
|||
output[i] = input[i] * 2; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector128<int>> input, |
|||
ref Av1TransformVector<Vector128<int>> output, |
|||
ref Av1TransformVector<Vector128<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 8, 2, 0); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Transform( |
|||
ref Av1TransformVector<Vector256<int>> input, |
|||
ref Av1TransformVector<Vector256<int>> output, |
|||
ref Av1TransformVector<Vector256<int>> step, |
|||
int cosBit, |
|||
Av1TransformStageRange stageRange) |
|||
{ |
|||
Av1IdentityTransform1d.Transform(ref input, ref output, 8, 2, 0); |
|||
_ = step; |
|||
_ = cosBit; |
|||
_ = stageRange; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <content>
|
|||
/// Provides the sample-output operator shared by inverse transform traversals.
|
|||
/// </content>
|
|||
internal static partial class Av1Inverse2dTransformer |
|||
{ |
|||
/// <summary>
|
|||
/// Reconstructs AV1 samples from predicted values and inverse-transform residuals.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each SIMD lane represents one consecutive reconstructed sample. Packed byte or 16-bit predictions are widened to
|
|||
/// signed 32-bit lanes before residual addition, clipped to the coded sample range, and narrowed into exact-width
|
|||
/// stores. The closed <typeparamref name="TSample"/> specialization removes storage-type branches from hot loops.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TSample">The decoded sample storage type.</typeparam>
|
|||
internal readonly struct OutputOperator<TSample> : IAv1InverseTransformOutputOperator<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static TSample Add(TSample prediction, int residual, int bitDepth) |
|||
{ |
|||
// TSample is fixed by the byte and short decoder entry points. The JIT removes this type test from each
|
|||
// closed transform so storage selection does not introduce a branch in the reconstruction loop.
|
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
byte value = (byte)Math.Clamp(Unsafe.As<TSample, byte>(ref prediction) + residual, byte.MinValue, byte.MaxValue); |
|||
return Unsafe.As<byte, TSample>(ref value); |
|||
} |
|||
|
|||
short result = (short)Math.Clamp(Unsafe.As<TSample, short>(ref prediction) + residual, 0, (1 << bitDepth) - 1); |
|||
return Unsafe.As<short, TSample>(ref result); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add(ref TSample prediction, ref TSample destination, Vector128<int> residual, int bitDepth) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
// Read and write exactly four bytes. The unused upper lanes only participate in narrowing and never reach
|
|||
// memory, which keeps reconstruction valid at a tightly packed row boundary.
|
|||
ref byte source = ref Unsafe.As<TSample, byte>(ref prediction); |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref source); |
|||
Vector128<ushort> predicted16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector128<int> predicted32 = Vector128.WidenLower(predicted16).AsInt32(); |
|||
Vector128<int> reconstructed = Vector128.Clamp(predicted32 + residual, Vector128<int>.Zero, Vector128.Create((int)byte.MaxValue)); |
|||
Vector128<ushort> reconstructed16 = Vector128.Narrow(reconstructed.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<byte> reconstructed8 = Vector128.Narrow(reconstructed16, Vector128<ushort>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), reconstructed8.AsUInt32().ToScalar()); |
|||
return; |
|||
} |
|||
|
|||
ref short highBitDepthSource = ref Unsafe.As<TSample, short>(ref prediction); |
|||
ulong highBitDepthPacked = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<short, byte>(ref highBitDepthSource)); |
|||
Vector128<int> highBitDepthPredicted = Vector128.WidenLower(Vector128.CreateScalarUnsafe(highBitDepthPacked).AsInt16()); |
|||
Vector128<int> highBitDepthReconstructed = |
|||
Vector128.Clamp(highBitDepthPredicted + residual, Vector128<int>.Zero, Vector128.Create((1 << bitDepth) - 1)); |
|||
|
|||
Vector128<short> narrowed = Vector128.Narrow(highBitDepthReconstructed, Vector128<int>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), narrowed.AsUInt64().ToScalar()); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add(ref TSample prediction, ref TSample destination, Vector256<int> residual, int bitDepth) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
// Eight byte predictions widen through UInt16 into the eight Int32 residual lanes. The final 64-bit store
|
|||
// covers only those reconstructed samples and does not require destination padding.
|
|||
ref byte source = ref Unsafe.As<TSample, byte>(ref prediction); |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source); |
|||
Vector128<ushort> predicted16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector256<int> predicted32 = Vector256.Create(Vector128.WidenLower(predicted16), Vector128.WidenUpper(predicted16)).AsInt32(); |
|||
Vector256<int> reconstructed = Vector256.Clamp(predicted32 + residual, Vector256<int>.Zero, Vector256.Create((int)byte.MaxValue)); |
|||
Vector128<ushort> reconstructed16 = Vector128.Narrow(reconstructed.GetLower().AsUInt32(), reconstructed.GetUpper().AsUInt32()); |
|||
Vector128<byte> reconstructed8 = Vector128.Narrow(reconstructed16, Vector128<ushort>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), reconstructed8.AsUInt64().ToScalar()); |
|||
return; |
|||
} |
|||
|
|||
ref short highBitDepthSource = ref Unsafe.As<TSample, short>(ref prediction); |
|||
Vector256<int> highBitDepthPredicted = Vector256_.Widen(Vector128.LoadUnsafe(ref highBitDepthSource)); |
|||
Vector256<int> highBitDepthReconstructed = |
|||
Vector256.Clamp(highBitDepthPredicted + residual, Vector256<int>.Zero, Vector256.Create((1 << bitDepth) - 1)); |
|||
|
|||
Vector128<short> narrowed = Vector128.Narrow(highBitDepthReconstructed.GetLower(), highBitDepthReconstructed.GetUpper()); |
|||
narrowed.StoreUnsafe(ref Unsafe.As<TSample, short>(ref destination)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs AV1 samples from predicted values and inverse-transform residuals.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Each SIMD lane represents one consecutive reconstructed sample. Packed byte or 16-bit predictions are widened to
|
|||
/// signed 32-bit lanes before residual addition, clipped to the coded sample range, and narrowed into exact-width
|
|||
/// stores. The closed <typeparamref name="TSample"/> specialization removes storage-type branches from hot loops.
|
|||
/// </remarks>
|
|||
/// <typeparam name="TSample">The decoded sample storage type.</typeparam>
|
|||
internal readonly struct Av1InverseTransformOutputOperator<TSample> : IAv1InverseTransformOutputOperator<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static TSample Add(TSample prediction, int residual, int bitDepth) |
|||
{ |
|||
// TSample is fixed by the byte and short decoder entry points. The JIT removes this type test from each
|
|||
// closed transform so storage selection does not introduce a branch in the reconstruction loop.
|
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
byte value = (byte)Math.Clamp(Unsafe.As<TSample, byte>(ref prediction) + residual, byte.MinValue, byte.MaxValue); |
|||
return Unsafe.As<byte, TSample>(ref value); |
|||
} |
|||
|
|||
short result = (short)Math.Clamp(Unsafe.As<TSample, short>(ref prediction) + residual, 0, (1 << bitDepth) - 1); |
|||
return Unsafe.As<short, TSample>(ref result); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add(ref TSample prediction, ref TSample destination, Vector128<int> residual, int bitDepth) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
// Read and write exactly four bytes. The unused upper lanes only participate in narrowing and never reach
|
|||
// memory, which keeps reconstruction valid at a tightly packed row boundary.
|
|||
ref byte source = ref Unsafe.As<TSample, byte>(ref prediction); |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref source); |
|||
Vector128<ushort> predicted16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector128<int> predicted32 = Vector128.WidenLower(predicted16).AsInt32(); |
|||
Vector128<int> reconstructed = Vector128.Clamp(predicted32 + residual, Vector128<int>.Zero, Vector128.Create((int)byte.MaxValue)); |
|||
Vector128<ushort> reconstructed16 = Vector128.Narrow(reconstructed.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<byte> reconstructed8 = Vector128.Narrow(reconstructed16, Vector128<ushort>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), reconstructed8.AsUInt32().ToScalar()); |
|||
return; |
|||
} |
|||
|
|||
ref short highBitDepthSource = ref Unsafe.As<TSample, short>(ref prediction); |
|||
ulong highBitDepthPacked = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<short, byte>(ref highBitDepthSource)); |
|||
Vector128<int> highBitDepthPredicted = Vector128.WidenLower(Vector128.CreateScalarUnsafe(highBitDepthPacked).AsInt16()); |
|||
Vector128<int> highBitDepthReconstructed = |
|||
Vector128.Clamp(highBitDepthPredicted + residual, Vector128<int>.Zero, Vector128.Create((1 << bitDepth) - 1)); |
|||
|
|||
Vector128<short> narrowed = Vector128.Narrow(highBitDepthReconstructed, Vector128<int>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), narrowed.AsUInt64().ToScalar()); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Add(ref TSample prediction, ref TSample destination, Vector256<int> residual, int bitDepth) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
// Eight byte predictions widen through UInt16 into the eight Int32 residual lanes. The final 64-bit store
|
|||
// covers only those reconstructed samples and does not require destination padding.
|
|||
ref byte source = ref Unsafe.As<TSample, byte>(ref prediction); |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source); |
|||
Vector128<ushort> predicted16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector256<int> predicted32 = Vector256.Create(Vector128.WidenLower(predicted16), Vector128.WidenUpper(predicted16)).AsInt32(); |
|||
Vector256<int> reconstructed = Vector256.Clamp(predicted32 + residual, Vector256<int>.Zero, Vector256.Create((int)byte.MaxValue)); |
|||
Vector128<ushort> reconstructed16 = Vector128.Narrow(reconstructed.GetLower().AsUInt32(), reconstructed.GetUpper().AsUInt32()); |
|||
Vector128<byte> reconstructed8 = Vector128.Narrow(reconstructed16, Vector128<ushort>.Zero); |
|||
Unsafe.WriteUnaligned(ref Unsafe.As<TSample, byte>(ref destination), reconstructed8.AsUInt64().ToScalar()); |
|||
return; |
|||
} |
|||
|
|||
ref short highBitDepthSource = ref Unsafe.As<TSample, short>(ref prediction); |
|||
Vector256<int> highBitDepthPredicted = Vector256_.Widen(Vector128.LoadUnsafe(ref highBitDepthSource)); |
|||
Vector256<int> highBitDepthReconstructed = |
|||
Vector256.Clamp(highBitDepthPredicted + residual, Vector256<int>.Zero, Vector256.Create((1 << bitDepth) - 1)); |
|||
|
|||
Vector128<short> narrowed = Vector128.Narrow(highBitDepthReconstructed.GetLower(), highBitDepthReconstructed.GetUpper()); |
|||
narrowed.StoreUnsafe(ref Unsafe.As<TSample, short>(ref destination)); |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Adst16Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the four-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Adst4Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst4(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 forward asymmetric discrete sine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Adst8Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Adst8(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Dct16Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the thirty-two-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Dct32Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct32(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the four-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Dct4Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct4(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the sixty-four-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Dct64Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct64(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the eight-point AV1 forward discrete cosine transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Dct8Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Dct8(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; |
|||
|
|||
/// <summary>
|
|||
/// Defines the sixteen-point AV1 forward identity transform operator.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Identity16Forward1dOperator : IAv1ForwardTransform1dOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static void Transform<TValue>( |
|||
ref byte values, |
|||
nint inputStride, |
|||
nint outputStride, |
|||
ref Av1TransformVector<TValue> buffer0, |
|||
ref Av1TransformVector<TValue> buffer1, |
|||
int cosBit) |
|||
where TValue : struct |
|||
=> Av1ForwardTransformOperations.Identity16(ref values, inputStride, outputStride, ref buffer0, ref buffer1, cosBit); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue