mirror of https://github.com/SixLabors/ImageSharp
47 changed files with 1727 additions and 428 deletions
@ -0,0 +1,334 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Components; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|||
|
|||
/// <summary>
|
|||
/// Provides non-owning visible and coded views over operation-scoped AV1 component planes.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The native unsigned sample storage type.</typeparam>
|
|||
internal readonly struct Av1EncoderFrame<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// The base-two alignment exponent applied to coded frame dimensions.
|
|||
/// </summary>
|
|||
private const int CodedDimensionAlignmentLog2 = 3; |
|||
|
|||
/// <summary>
|
|||
/// The base-two alignment exponent applied to the physical luma row stride.
|
|||
/// </summary>
|
|||
private const int LumaStrideAlignmentLog2 = 5; |
|||
|
|||
/// <summary>
|
|||
/// The physical luma border required by non-resized all-intra encoding.
|
|||
/// </summary>
|
|||
public const int LumaBorder = 64; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1EncoderFrame{TSample}"/> struct for a monochrome frame.
|
|||
/// </summary>
|
|||
/// <param name="luma">The coded luma region inside the bordered plane owned by the encode operation.</param>
|
|||
/// <param name="width">The visible luma width.</param>
|
|||
/// <param name="height">The visible luma height.</param>
|
|||
/// <param name="bitDepth">The native component precision.</param>
|
|||
public Av1EncoderFrame(Buffer2DRegion<TSample> luma, int width, int height, int bitDepth) |
|||
: this(luma, default, default, width, height, bitDepth, Av1ColorFormat.Yuv400, 0, 0) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1EncoderFrame{TSample}"/> struct for a color frame.
|
|||
/// </summary>
|
|||
/// <param name="luma">The coded luma region inside the bordered plane owned by the encode operation.</param>
|
|||
/// <param name="chromaBlue">The coded blue-difference region inside its bordered plane.</param>
|
|||
/// <param name="chromaRed">The coded red-difference region inside its bordered plane.</param>
|
|||
/// <param name="width">The visible luma width.</param>
|
|||
/// <param name="height">The visible luma height.</param>
|
|||
/// <param name="bitDepth">The native component precision.</param>
|
|||
/// <param name="colorFormat">The native luma and chroma sampling layout.</param>
|
|||
/// <param name="chromaPositionX">The horizontal chroma position in half-luma-sample units.</param>
|
|||
/// <param name="chromaPositionY">The vertical chroma position in half-luma-sample units.</param>
|
|||
public Av1EncoderFrame( |
|||
Buffer2DRegion<TSample> luma, |
|||
Buffer2DRegion<TSample> chromaBlue, |
|||
Buffer2DRegion<TSample> chromaRed, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1ColorFormat colorFormat, |
|||
int chromaPositionX, |
|||
int chromaPositionY) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.LumaBitDepth = bitDepth; |
|||
this.ChromaBitDepth = bitDepth; |
|||
this.IsMonochrome = colorFormat == Av1ColorFormat.Yuv400; |
|||
this.ChromaSubsamplingX = colorFormat is Av1ColorFormat.Yuv420 or Av1ColorFormat.Yuv422 ? 1 : 0; |
|||
this.ChromaSubsamplingY = colorFormat == Av1ColorFormat.Yuv420 ? 1 : 0; |
|||
this.ChromaPositionX = chromaPositionX; |
|||
this.ChromaPositionY = chromaPositionY; |
|||
this.CodedWidth = luma.Width; |
|||
this.CodedHeight = luma.Height; |
|||
int visibleChromaWidth = (width + this.ChromaSubsamplingX) >> this.ChromaSubsamplingX; |
|||
int visibleChromaHeight = (height + this.ChromaSubsamplingY) >> this.ChromaSubsamplingY; |
|||
Buffer2DRegion<TSample> visibleChromaBlue = this.IsMonochrome |
|||
? default |
|||
: chromaBlue.GetSubRegion(0, 0, visibleChromaWidth, visibleChromaHeight); |
|||
|
|||
Buffer2DRegion<TSample> visibleChromaRed = this.IsMonochrome |
|||
? default |
|||
: chromaRed.GetSubRegion(0, 0, visibleChromaWidth, visibleChromaHeight); |
|||
|
|||
this.View = new PlanarView( |
|||
luma.GetSubRegion(0, 0, width, height), |
|||
visibleChromaBlue, |
|||
visibleChromaRed, |
|||
width, |
|||
height, |
|||
bitDepth, |
|||
colorFormat, |
|||
chromaPositionX, |
|||
chromaPositionY); |
|||
|
|||
this.CodedView = new PlanarView( |
|||
luma, |
|||
chromaBlue, |
|||
chromaRed, |
|||
this.CodedWidth, |
|||
this.CodedHeight, |
|||
bitDepth, |
|||
colorFormat, |
|||
chromaPositionX, |
|||
chromaPositionY); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the writable component-plane view used by closed generic conversion and coding operations.
|
|||
/// </summary>
|
|||
public PlanarView View { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the writable coded component planes used by block coding and reconstruction.
|
|||
/// </summary>
|
|||
public PlanarView CodedView { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the visible luma width.
|
|||
/// </summary>
|
|||
public int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the visible luma height.
|
|||
/// </summary>
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma width rounded up to the fixed coding-block boundary.
|
|||
/// </summary>
|
|||
public int CodedWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the luma height rounded up to the fixed coding-block boundary.
|
|||
/// </summary>
|
|||
public int CodedHeight { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the native luma sample precision.
|
|||
/// </summary>
|
|||
public int LumaBitDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the native chroma sample precision.
|
|||
/// </summary>
|
|||
public int ChromaBitDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the frame contains only luma samples.
|
|||
/// </summary>
|
|||
public bool IsMonochrome { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal chroma subsampling shift.
|
|||
/// </summary>
|
|||
public int ChromaSubsamplingX { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical chroma subsampling shift.
|
|||
/// </summary>
|
|||
public int ChromaSubsamplingY { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the horizontal chroma position in half-luma-sample units.
|
|||
/// </summary>
|
|||
public int ChromaPositionX { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the vertical chroma position in half-luma-sample units.
|
|||
/// </summary>
|
|||
public int ChromaPositionY { get; } |
|||
|
|||
/// <summary>
|
|||
/// Calculates the physical dimensions required for an all-intra component plane.
|
|||
/// </summary>
|
|||
/// <param name="width">The visible luma width.</param>
|
|||
/// <param name="height">The visible luma height.</param>
|
|||
/// <param name="subsamplingX">The plane's horizontal subsampling shift.</param>
|
|||
/// <param name="subsamplingY">The plane's vertical subsampling shift.</param>
|
|||
/// <returns>The physical plane dimensions, including its complete border and row padding.</returns>
|
|||
public static Size GetPlaneBufferSize(int width, int height, int subsamplingX, int subsamplingY) |
|||
{ |
|||
int codedWidth = Av1Math.AlignPowerOf2(width, CodedDimensionAlignmentLog2); |
|||
int codedHeight = Av1Math.AlignPowerOf2(height, CodedDimensionAlignmentLog2); |
|||
|
|||
// libaom aligns the complete luma row before deriving a subsampled plane's stride.
|
|||
// Aligning chroma independently would produce a different physical layout for narrow or odd-sized frames.
|
|||
int lumaStride = Av1Math.AlignPowerOf2(codedWidth + (2 * LumaBorder), LumaStrideAlignmentLog2); |
|||
int planeStride = lumaStride >> subsamplingX; |
|||
int planeBorderHeight = LumaBorder >> subsamplingY; |
|||
return new Size(planeStride, (codedHeight >> subsamplingY) + (2 * planeBorderHeight)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Extends the visible edge samples through the coded padding.
|
|||
/// </summary>
|
|||
public void ExtendBorders() |
|||
=> this.CodedView.ExtendBorders(this.Width, this.Height); |
|||
|
|||
/// <summary>
|
|||
/// Replicates the visible edge samples through a plane's complete physical border.
|
|||
/// </summary>
|
|||
private static void ExtendPlane(Buffer2DRegion<TSample> plane, int visibleWidth, int visibleHeight) |
|||
{ |
|||
Buffer2D<TSample> buffer = plane.Buffer; |
|||
Rectangle bounds = plane.Bounds; |
|||
for (int y = 0; y < visibleHeight; y++) |
|||
{ |
|||
Span<TSample> row = buffer.DangerousGetRowSpan(bounds.Y + y); |
|||
|
|||
// libaom fills both physical borders and the right-hand coded alignment from the nearest visible sample.
|
|||
row[..bounds.X].Fill(row[bounds.X]); |
|||
row[(bounds.X + visibleWidth)..].Fill(row[bounds.X + visibleWidth - 1]); |
|||
} |
|||
|
|||
// Horizontal extension runs first so copying the first and last visible rows also initializes both corners.
|
|||
ReadOnlySpan<TSample> firstVisibleRow = buffer.DangerousGetRowSpan(bounds.Y); |
|||
for (int y = 0; y < bounds.Y; y++) |
|||
{ |
|||
firstVisibleRow.CopyTo(buffer.DangerousGetRowSpan(y)); |
|||
} |
|||
|
|||
ReadOnlySpan<TSample> finalVisibleRow = buffer.DangerousGetRowSpan(bounds.Y + visibleHeight - 1); |
|||
for (int y = bounds.Y + visibleHeight; y < buffer.Height; y++) |
|||
{ |
|||
finalVisibleRow.CopyTo(buffer.DangerousGetRowSpan(y)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Provides a non-owning component-plane view for generic hot-path operations.
|
|||
/// </summary>
|
|||
internal readonly struct PlanarView : IHeifPlanarSampleBuffer<TSample> |
|||
{ |
|||
/// <summary>
|
|||
/// The writable luma plane.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<TSample> luma; |
|||
|
|||
/// <summary>
|
|||
/// The writable blue-difference plane, or the default region for monochrome frames.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<TSample> chromaBlue; |
|||
|
|||
/// <summary>
|
|||
/// The writable red-difference plane, or the default region for monochrome frames.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<TSample> chromaRed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PlanarView"/> struct.
|
|||
/// </summary>
|
|||
public PlanarView( |
|||
Buffer2DRegion<TSample> luma, |
|||
Buffer2DRegion<TSample> chromaBlue, |
|||
Buffer2DRegion<TSample> chromaRed, |
|||
int width, |
|||
int height, |
|||
int bitDepth, |
|||
Av1ColorFormat colorFormat, |
|||
int chromaPositionX, |
|||
int chromaPositionY) |
|||
{ |
|||
this.luma = luma; |
|||
this.chromaBlue = chromaBlue; |
|||
this.chromaRed = chromaRed; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.LumaBitDepth = bitDepth; |
|||
this.ChromaBitDepth = bitDepth; |
|||
this.IsMonochrome = colorFormat == Av1ColorFormat.Yuv400; |
|||
this.ChromaSubsamplingX = colorFormat is Av1ColorFormat.Yuv420 or Av1ColorFormat.Yuv422 ? 1 : 0; |
|||
this.ChromaSubsamplingY = colorFormat == Av1ColorFormat.Yuv420 ? 1 : 0; |
|||
this.ChromaPositionX = chromaPositionX; |
|||
this.ChromaPositionY = chromaPositionY; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public int Width { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int Height { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int LumaBitDepth { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ChromaBitDepth { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool IsMonochrome { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ChromaSubsamplingX { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ChromaSubsamplingY { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ChromaPositionX { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public int ChromaPositionY { get; } |
|||
|
|||
/// <summary>
|
|||
/// Replicates the visible component edges through the coded padding.
|
|||
/// </summary>
|
|||
/// <param name="visibleWidth">The visible luma width.</param>
|
|||
/// <param name="visibleHeight">The visible luma height.</param>
|
|||
public void ExtendBorders(int visibleWidth, int visibleHeight) |
|||
{ |
|||
ExtendPlane(this.luma, visibleWidth, visibleHeight); |
|||
|
|||
if (!this.IsMonochrome) |
|||
{ |
|||
int visibleChromaWidth = (visibleWidth + this.ChromaSubsamplingX) >> this.ChromaSubsamplingX; |
|||
int visibleChromaHeight = (visibleHeight + this.ChromaSubsamplingY) >> this.ChromaSubsamplingY; |
|||
ExtendPlane(this.chromaBlue, visibleChromaWidth, visibleChromaHeight); |
|||
ExtendPlane(this.chromaRed, visibleChromaWidth, visibleChromaHeight); |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<TSample> GetLumaRowSpan(int row) => this.luma.DangerousGetRowSpan(row); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<TSample> GetChromaBlueRowSpan(int row) => this.chromaBlue.DangerousGetRowSpan(row); |
|||
|
|||
/// <inheritdoc/>
|
|||
public Span<TSample> GetChromaRedRowSpan(int row) => this.chromaRed.DangerousGetRowSpan(row); |
|||
} |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers; |
|||
|
|||
/// <content>
|
|||
/// Defines the arithmetic operators used by <see cref="Av1ForwardQuantizer"/>.
|
|||
/// </content>
|
|||
internal static partial class Av1ForwardQuantizer |
|||
{ |
|||
/// <summary>
|
|||
/// Defines one AV1 forward-quantization arithmetic contract across hardware widths.
|
|||
/// </summary>
|
|||
internal interface IForwardQuantizationOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Quantizes four raster-order transform coefficients.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The signed transform coefficients.</param>
|
|||
/// <param name="rounding">The positive rounding constant after transform-size scaling.</param>
|
|||
/// <param name="quantizer">The Q16 reciprocal quantizer.</param>
|
|||
/// <param name="dequantizer">The Q3 reconstruction quantizer.</param>
|
|||
/// <param name="logScale">The transform-size quantization scale.</param>
|
|||
/// <param name="dequantizedCoefficients">The signed reconstruction coefficients.</param>
|
|||
/// <returns>The signed entropy-coding coefficients.</returns>
|
|||
public static abstract Vector128<int> Quantize( |
|||
Vector128<int> coefficients, |
|||
Vector128<int> rounding, |
|||
Vector128<int> quantizer, |
|||
Vector128<int> dequantizer, |
|||
int logScale, |
|||
out Vector128<int> dequantizedCoefficients); |
|||
|
|||
/// <summary>
|
|||
/// Quantizes eight raster-order transform coefficients.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The signed transform coefficients.</param>
|
|||
/// <param name="rounding">The positive rounding constant after transform-size scaling.</param>
|
|||
/// <param name="quantizer">The Q16 reciprocal quantizer.</param>
|
|||
/// <param name="dequantizer">The Q3 reconstruction quantizer.</param>
|
|||
/// <param name="logScale">The transform-size quantization scale.</param>
|
|||
/// <param name="dequantizedCoefficients">The signed reconstruction coefficients.</param>
|
|||
/// <returns>The signed entropy-coding coefficients.</returns>
|
|||
public static abstract Vector256<int> Quantize( |
|||
Vector256<int> coefficients, |
|||
Vector256<int> rounding, |
|||
Vector256<int> quantizer, |
|||
Vector256<int> dequantizer, |
|||
int logScale, |
|||
out Vector256<int> dequantizedCoefficients); |
|||
|
|||
/// <summary>
|
|||
/// Quantizes sixteen raster-order transform coefficients.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The signed transform coefficients.</param>
|
|||
/// <param name="rounding">The positive rounding constant after transform-size scaling.</param>
|
|||
/// <param name="quantizer">The Q16 reciprocal quantizer.</param>
|
|||
/// <param name="dequantizer">The Q3 reconstruction quantizer.</param>
|
|||
/// <param name="logScale">The transform-size quantization scale.</param>
|
|||
/// <param name="dequantizedCoefficients">The signed reconstruction coefficients.</param>
|
|||
/// <returns>The signed entropy-coding coefficients.</returns>
|
|||
public static abstract Vector512<int> Quantize( |
|||
Vector512<int> coefficients, |
|||
Vector512<int> rounding, |
|||
Vector512<int> quantizer, |
|||
Vector512<int> dequantizer, |
|||
int logScale, |
|||
out Vector512<int> dequantizedCoefficients); |
|||
|
|||
/// <summary>
|
|||
/// Quantizes one transform coefficient.
|
|||
/// </summary>
|
|||
/// <param name="coefficient">The signed transform coefficient.</param>
|
|||
/// <param name="rounding">The positive rounding constant after transform-size scaling.</param>
|
|||
/// <param name="quantizer">The Q16 reciprocal quantizer.</param>
|
|||
/// <param name="dequantizer">The Q3 reconstruction quantizer.</param>
|
|||
/// <param name="logScale">The transform-size quantization scale.</param>
|
|||
/// <param name="dequantizedCoefficient">The signed reconstruction coefficient.</param>
|
|||
/// <returns>The signed entropy-coding coefficient.</returns>
|
|||
public static abstract int Quantize( |
|||
int coefficient, |
|||
int rounding, |
|||
int quantizer, |
|||
int dequantizer, |
|||
int logScale, |
|||
out int dequantizedCoefficient); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements libaom's fast no-matrix quantizer for lossy transform blocks.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// Every SIMD overload preserves the scalar operation order: magnitude threshold, saturating round, Q16 reciprocal
|
|||
/// multiply, transform-size shift, dequantization, and sign restoration. Each lane owns one raster-order coefficient.
|
|||
/// </remarks>
|
|||
internal readonly struct FastQuantizationOperator : IForwardQuantizationOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<int> Quantize( |
|||
Vector128<int> coefficients, |
|||
Vector128<int> rounding, |
|||
Vector128<int> quantizer, |
|||
Vector128<int> dequantizer, |
|||
int logScale, |
|||
out Vector128<int> dequantizedCoefficients) |
|||
{ |
|||
Vector128<int> coefficientSign = Vector128.ShiftRightArithmetic(coefficients, 31); |
|||
Vector128<int> magnitude = Vector128.Abs(coefficients); |
|||
|
|||
// libaom retains equality at the dequantizer threshold. Reversing the comparison and complementing its mask
|
|||
// expresses scaledMagnitude >= dequantizer with the vector operations available for every supported ISA.
|
|||
Vector128<int> thresholdMask = ~Vector128.GreaterThan(dequantizer, magnitude << (1 + logScale)); |
|||
|
|||
// Clamp the rounded magnitude to 32,767 so the following Q16 product remains within a signed 32-bit lane.
|
|||
Vector128<int> rounded = Vector128.Min(magnitude + rounding, Vector128.Create((int)short.MaxValue)); |
|||
Vector128<int> quantizedMagnitude = ((rounded * quantizer) >> (16 - logScale)) & thresholdMask; |
|||
|
|||
// XOR followed by subtraction restores the original sign without a lane-wise branch.
|
|||
Vector128<int> quantized = (quantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
Vector128<int> dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale; |
|||
dequantizedCoefficients = (dequantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
return quantized; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<int> Quantize( |
|||
Vector256<int> coefficients, |
|||
Vector256<int> rounding, |
|||
Vector256<int> quantizer, |
|||
Vector256<int> dequantizer, |
|||
int logScale, |
|||
out Vector256<int> dequantizedCoefficients) |
|||
{ |
|||
Vector256<int> coefficientSign = Vector256.ShiftRightArithmetic(coefficients, 31); |
|||
Vector256<int> magnitude = Vector256.Abs(coefficients); |
|||
|
|||
// Preserve threshold equality by complementing dequantizer > scaledMagnitude.
|
|||
Vector256<int> thresholdMask = ~Vector256.GreaterThan(dequantizer, magnitude << (1 + logScale)); |
|||
|
|||
// Clamp the rounded magnitude to 32,767 so the following Q16 product remains within a signed 32-bit lane.
|
|||
Vector256<int> rounded = Vector256.Min(magnitude + rounding, Vector256.Create((int)short.MaxValue)); |
|||
Vector256<int> quantizedMagnitude = ((rounded * quantizer) >> (16 - logScale)) & thresholdMask; |
|||
|
|||
// Apply the input sign to both coded and reconstructed magnitudes without branching.
|
|||
Vector256<int> quantized = (quantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
Vector256<int> dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale; |
|||
dequantizedCoefficients = (dequantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
return quantized; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<int> Quantize( |
|||
Vector512<int> coefficients, |
|||
Vector512<int> rounding, |
|||
Vector512<int> quantizer, |
|||
Vector512<int> dequantizer, |
|||
int logScale, |
|||
out Vector512<int> dequantizedCoefficients) |
|||
{ |
|||
Vector512<int> coefficientSign = Vector512.ShiftRightArithmetic(coefficients, 31); |
|||
Vector512<int> magnitude = Vector512.Abs(coefficients); |
|||
|
|||
// Preserve threshold equality by complementing dequantizer > scaledMagnitude.
|
|||
Vector512<int> thresholdMask = ~Vector512.GreaterThan(dequantizer, magnitude << (1 + logScale)); |
|||
|
|||
// Clamp the rounded magnitude to 32,767 so the following Q16 product remains within a signed 32-bit lane.
|
|||
Vector512<int> rounded = Vector512.Min(magnitude + rounding, Vector512.Create((int)short.MaxValue)); |
|||
Vector512<int> quantizedMagnitude = ((rounded * quantizer) >> (16 - logScale)) & thresholdMask; |
|||
|
|||
// Apply the input sign to both coded and reconstructed magnitudes without branching.
|
|||
Vector512<int> quantized = (quantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
Vector512<int> dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale; |
|||
dequantizedCoefficients = (dequantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
return quantized; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static int Quantize( |
|||
int coefficient, |
|||
int rounding, |
|||
int quantizer, |
|||
int dequantizer, |
|||
int logScale, |
|||
out int dequantizedCoefficient) |
|||
{ |
|||
int coefficientSign = coefficient >> 31; |
|||
int magnitude = (coefficient ^ coefficientSign) - coefficientSign; |
|||
int quantizedMagnitude = 0; |
|||
|
|||
// The scalar tail keeps the same threshold, clamp, and fixed-point operation order as every vector lane.
|
|||
if (((long)magnitude << (1 + logScale)) >= dequantizer) |
|||
{ |
|||
int rounded = Math.Min(magnitude + rounding, short.MaxValue); |
|||
quantizedMagnitude = (rounded * quantizer) >> (16 - logScale); |
|||
} |
|||
|
|||
int quantized = (quantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
int dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale; |
|||
dequantizedCoefficient = (dequantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
return quantized; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,209 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers; |
|||
|
|||
/// <summary>
|
|||
/// Applies AV1 forward quantization to raster-order transform coefficients.
|
|||
/// </summary>
|
|||
internal static partial class Av1ForwardQuantizer |
|||
{ |
|||
/// <summary>
|
|||
/// Quantizes one lossy transform block with libaom's fast no-matrix arithmetic.
|
|||
/// </summary>
|
|||
/// <param name="coefficients">The raster-order forward-transform coefficients.</param>
|
|||
/// <param name="quantizedCoefficients">The raster-order entropy-coding coefficients.</param>
|
|||
/// <param name="dequantizedCoefficients">The raster-order reconstruction coefficients.</param>
|
|||
/// <param name="transformSize">The transform-block dimensions.</param>
|
|||
/// <param name="transformType">The compound transform type.</param>
|
|||
/// <param name="qIndex">The segment quantizer index.</param>
|
|||
/// <param name="dcDeltaQ">The plane DC quantizer adjustment.</param>
|
|||
/// <param name="acDeltaQ">The plane AC quantizer adjustment.</param>
|
|||
/// <param name="bitDepth">The coded sample bit depth.</param>
|
|||
/// <returns>The one-based end position in coefficient scan order.</returns>
|
|||
public static ushort QuantizeLossy( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> quantizedCoefficients, |
|||
Span<int> dequantizedCoefficients, |
|||
Av1TransformSize transformSize, |
|||
Av1TransformType transformType, |
|||
int qIndex, |
|||
int dcDeltaQ, |
|||
int acDeltaQ, |
|||
Av1BitDepth bitDepth) |
|||
=> QuantizeLossy<FastQuantizationOperator>( |
|||
coefficients, |
|||
quantizedCoefficients, |
|||
dequantizedCoefficients, |
|||
transformSize, |
|||
transformType, |
|||
qIndex, |
|||
dcDeltaQ, |
|||
acDeltaQ, |
|||
bitDepth); |
|||
|
|||
/// <summary>
|
|||
/// Applies a closed generic quantization operator across the widest available hardware widths.
|
|||
/// </summary>
|
|||
internal static ushort QuantizeLossy<TOperator>( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> quantizedCoefficients, |
|||
Span<int> dequantizedCoefficients, |
|||
Av1TransformSize transformSize, |
|||
Av1TransformType transformType, |
|||
int qIndex, |
|||
int dcDeltaQ, |
|||
int acDeltaQ, |
|||
Av1BitDepth bitDepth) |
|||
where TOperator : struct, IForwardQuantizationOperator |
|||
{ |
|||
int coefficientCount = transformSize.GetAdjusted().GetSize2d(); |
|||
int logScale = transformSize.GetScale(); |
|||
int dcDequantizer = Av1QuantizationLookup.GetDcQuant(qIndex, dcDeltaQ, bitDepth); |
|||
int acDequantizer = Av1QuantizationLookup.GetAcQuant(qIndex, acDeltaQ, bitDepth); |
|||
int dcQuantizer = (1 << 16) / dcDequantizer; |
|||
int acQuantizer = (1 << 16) / acDequantizer; |
|||
int dcRounding = RoundPowerOfTwo((64 * dcDequantizer) >> 7, logScale); |
|||
int acRounding = RoundPowerOfTwo((64 * acDequantizer) >> 7, logScale); |
|||
|
|||
ref int sourceBase = ref MemoryMarshal.GetReference(coefficients); |
|||
ref int quantizedBase = ref MemoryMarshal.GetReference(quantizedCoefficients); |
|||
ref int dequantizedBase = ref MemoryMarshal.GetReference(dequantizedCoefficients); |
|||
|
|||
// Raster coefficient zero is the only DC coefficient, so it is encoded once with the plane's DC constants
|
|||
// before the AC-only SIMD traversal begins.
|
|||
Unsafe.Add(ref quantizedBase, 0) = TOperator.Quantize( |
|||
Unsafe.Add(ref sourceBase, 0), |
|||
dcRounding, |
|||
dcQuantizer, |
|||
dcDequantizer, |
|||
logScale, |
|||
out Unsafe.Add(ref dequantizedBase, 0)); |
|||
|
|||
int i = 1; |
|||
|
|||
// Raster traversal keeps loads and stores contiguous. Each narrower tier resumes at the shared offset left by
|
|||
// the previous tier, retaining vector execution for the widest possible remainder without overlapping lanes.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
nuint vectorCount = coefficients[i..coefficientCount].Vector512Count<int>(); |
|||
|
|||
if (vectorCount > 0) |
|||
{ |
|||
// Width-specific constants are created only when at least one complete vector remains.
|
|||
Vector512<int> rounding = Vector512.Create(acRounding); |
|||
Vector512<int> quantizer = Vector512.Create(acQuantizer); |
|||
Vector512<int> dequantizer = Vector512.Create(acDequantizer); |
|||
|
|||
for (; vectorCount > 0; vectorCount--, i += Vector512<int>.Count) |
|||
{ |
|||
Vector512<int> source = Unsafe.As<int, Vector512<int>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector512<int> quantized = TOperator.Quantize( |
|||
source, |
|||
rounding, |
|||
quantizer, |
|||
dequantizer, |
|||
logScale, |
|||
out Vector512<int> dequantized); |
|||
|
|||
Unsafe.As<int, Vector512<int>>(ref Unsafe.Add(ref quantizedBase, i)) = quantized; |
|||
Unsafe.As<int, Vector512<int>>(ref Unsafe.Add(ref dequantizedBase, i)) = dequantized; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
nuint vectorCount = coefficients[i..coefficientCount].Vector256Count<int>(); |
|||
|
|||
if (vectorCount > 0) |
|||
{ |
|||
// The shared offset exposes only the remainder left by wider lanes, so no coefficient is revisited.
|
|||
Vector256<int> rounding = Vector256.Create(acRounding); |
|||
Vector256<int> quantizer = Vector256.Create(acQuantizer); |
|||
Vector256<int> dequantizer = Vector256.Create(acDequantizer); |
|||
|
|||
for (; vectorCount > 0; vectorCount--, i += Vector256<int>.Count) |
|||
{ |
|||
Vector256<int> source = Unsafe.As<int, Vector256<int>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector256<int> quantized = TOperator.Quantize( |
|||
source, |
|||
rounding, |
|||
quantizer, |
|||
dequantizer, |
|||
logScale, |
|||
out Vector256<int> dequantized); |
|||
|
|||
Unsafe.As<int, Vector256<int>>(ref Unsafe.Add(ref quantizedBase, i)) = quantized; |
|||
Unsafe.As<int, Vector256<int>>(ref Unsafe.Add(ref dequantizedBase, i)) = dequantized; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
nuint vectorCount = coefficients[i..coefficientCount].Vector128Count<int>(); |
|||
|
|||
if (vectorCount > 0) |
|||
{ |
|||
// The final SIMD tier consumes complete four-lane groups and leaves fewer than four coefficients.
|
|||
Vector128<int> rounding = Vector128.Create(acRounding); |
|||
Vector128<int> quantizer = Vector128.Create(acQuantizer); |
|||
Vector128<int> dequantizer = Vector128.Create(acDequantizer); |
|||
|
|||
for (; vectorCount > 0; vectorCount--, i += Vector128<int>.Count) |
|||
{ |
|||
Vector128<int> source = Unsafe.As<int, Vector128<int>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector128<int> quantized = TOperator.Quantize( |
|||
source, |
|||
rounding, |
|||
quantizer, |
|||
dequantizer, |
|||
logScale, |
|||
out Vector128<int> dequantized); |
|||
|
|||
Unsafe.As<int, Vector128<int>>(ref Unsafe.Add(ref quantizedBase, i)) = quantized; |
|||
Unsafe.As<int, Vector128<int>>(ref Unsafe.Add(ref dequantizedBase, i)) = dequantized; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// On SIMD-capable systems this loop receives only the final zero-to-three AC coefficients.
|
|||
for (; i < coefficientCount; i++) |
|||
{ |
|||
Unsafe.Add(ref quantizedBase, i) = TOperator.Quantize( |
|||
Unsafe.Add(ref sourceBase, i), |
|||
acRounding, |
|||
acQuantizer, |
|||
acDequantizer, |
|||
logScale, |
|||
out Unsafe.Add(ref dequantizedBase, i)); |
|||
} |
|||
|
|||
ReadOnlySpan<short> scan = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType).Scan; |
|||
|
|||
// Quantized coefficients remain in raster order for reconstruction and entropy coding. A reverse scan finds
|
|||
// the final nonzero position without another buffer, and normally exits on its first iteration at high quality.
|
|||
for (int scanIndex = coefficientCount - 1; scanIndex >= 0; scanIndex--) |
|||
{ |
|||
if (Unsafe.Add(ref quantizedBase, scan[scanIndex]) != 0) |
|||
{ |
|||
return (ushort)(scanIndex + 1); |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies libaom's positive round-power-of-two operation to one quantizer constant.
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RoundPowerOfTwo(int value, int shift) |
|||
=> shift == 0 ? value : (value + (1 << (shift - 1))) >> shift; |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; |
|||
using SixLabors.ImageSharp.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
public class Av1EncoderFrameTests |
|||
{ |
|||
[Fact] |
|||
public void ExtendBordersReplicatesEveryPhysicalPlaneEdge() |
|||
{ |
|||
const int visibleWidth = 5; |
|||
const int visibleHeight = 3; |
|||
const int codedWidth = 8; |
|||
const int codedHeight = 8; |
|||
const int lumaBorder = Av1EncoderFrame<byte>.LumaBorder; |
|||
const int chromaBorder = lumaBorder / 2; |
|||
MemoryAllocator allocator = Configuration.Default.MemoryAllocator; |
|||
|
|||
Size lumaBufferSize = Av1EncoderFrame<byte>.GetPlaneBufferSize(visibleWidth, visibleHeight, 0, 0); |
|||
Size chromaBufferSize = Av1EncoderFrame<byte>.GetPlaneBufferSize(visibleWidth, visibleHeight, 1, 1); |
|||
using Buffer2D<byte> luma = allocator.Allocate2D<byte>(lumaBufferSize.Width, lumaBufferSize.Height); |
|||
using Buffer2D<byte> chromaBlue = allocator.Allocate2D<byte>(chromaBufferSize.Width, chromaBufferSize.Height); |
|||
using Buffer2D<byte> chromaRed = allocator.Allocate2D<byte>(chromaBufferSize.Width, chromaBufferSize.Height); |
|||
Buffer2DRegion<byte> lumaRegion = luma.GetRegion(lumaBorder, lumaBorder, codedWidth, codedHeight); |
|||
Buffer2DRegion<byte> chromaBlueRegion = chromaBlue.GetRegion(chromaBorder, chromaBorder, codedWidth / 2, codedHeight / 2); |
|||
Buffer2DRegion<byte> chromaRedRegion = chromaRed.GetRegion(chromaBorder, chromaBorder, codedWidth / 2, codedHeight / 2); |
|||
|
|||
FillVisible(luma, lumaBorder, lumaBorder, visibleWidth, visibleHeight, 10); |
|||
FillVisible(chromaBlue, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 80); |
|||
FillVisible(chromaRed, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 120); |
|||
|
|||
Av1EncoderFrame<byte> frame = new( |
|||
lumaRegion, |
|||
chromaBlueRegion, |
|||
chromaRedRegion, |
|||
visibleWidth, |
|||
visibleHeight, |
|||
8, |
|||
Av1ColorFormat.Yuv420, |
|||
1, |
|||
1); |
|||
|
|||
frame.ExtendBorders(); |
|||
|
|||
AssertReplicatedPlane(luma, lumaBorder, lumaBorder, visibleWidth, visibleHeight, 10); |
|||
AssertReplicatedPlane(chromaBlue, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 80); |
|||
AssertReplicatedPlane(chromaRed, chromaBorder, chromaBorder, (visibleWidth + 1) / 2, (visibleHeight + 1) / 2, 120); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(5, 3, 0, 0, 160, 136)] |
|||
[InlineData(5, 3, 1, 0, 80, 136)] |
|||
[InlineData(5, 3, 1, 1, 80, 68)] |
|||
[InlineData(1921, 1081, 0, 0, 2080, 1216)] |
|||
[InlineData(1921, 1081, 1, 1, 1040, 608)] |
|||
public void GetPlaneBufferSizeMatchesLibaomLayout( |
|||
int width, |
|||
int height, |
|||
int subsamplingX, |
|||
int subsamplingY, |
|||
int expectedWidth, |
|||
int expectedHeight) |
|||
{ |
|||
Size actual = Av1EncoderFrame<byte>.GetPlaneBufferSize(width, height, subsamplingX, subsamplingY); |
|||
|
|||
Assert.Equal(new Size(expectedWidth, expectedHeight), actual); |
|||
} |
|||
|
|||
private static void FillVisible(Buffer2D<byte> plane, int originX, int originY, int width, int height, int seed) |
|||
{ |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
Span<byte> row = plane.DangerousGetRowSpan(originY + y); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
row[originX + x] = (byte)(seed + (y * width) + x); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static void AssertReplicatedPlane(Buffer2D<byte> plane, int originX, int originY, int width, int height, int seed) |
|||
{ |
|||
for (int y = 0; y < plane.Height; y++) |
|||
{ |
|||
ReadOnlySpan<byte> row = plane.DangerousGetRowSpan(y); |
|||
int sourceY = Math.Clamp(y - originY, 0, height - 1); |
|||
for (int x = 0; x < row.Length; x++) |
|||
{ |
|||
int sourceX = Math.Clamp(x - originX, 0, width - 1); |
|||
Assert.Equal((byte)(seed + (sourceY * width) + sourceX), row[x]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,212 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Verifies AV1 forward quantization against current libaom's fast no-matrix arithmetic.
|
|||
/// </summary>
|
|||
[Trait("Format", "Avif")] |
|||
public class Av1ForwardQuantizerTests |
|||
{ |
|||
/// <summary>
|
|||
/// The hardware configurations covering every quantizer vector tier and the scalar fallback.
|
|||
/// </summary>
|
|||
private const HwIntrinsics QuantizerConfigurations = |
|||
HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic; |
|||
|
|||
/// <summary>
|
|||
/// Verifies raster quantization and scan-order EOB selection at every SIMD tier.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void FastQuantizerMatchesLibaomReferenceAcrossHardwareWidths() |
|||
=> FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidateQuantizer, QuantizerConfigurations); |
|||
|
|||
/// <summary>
|
|||
/// Verifies that repeated transform quantization uses only caller-owned buffers.
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void QuantizerDoesNotAllocatePerTransform() |
|||
{ |
|||
const int coefficientCount = 64; |
|||
int[] coefficients = new int[coefficientCount]; |
|||
int[] quantized = new int[coefficientCount]; |
|||
int[] dequantized = new int[coefficientCount]; |
|||
FillCoefficients(coefficients, 73); |
|||
|
|||
Av1ForwardQuantizer.QuantizeLossy( |
|||
coefficients, |
|||
quantized, |
|||
dequantized, |
|||
Av1TransformSize.Size8x8, |
|||
Av1TransformType.DctDct, |
|||
73, |
|||
-1, |
|||
3, |
|||
Av1BitDepth.TenBit); |
|||
|
|||
long before = GC.GetAllocatedBytesForCurrentThread(); |
|||
for (int iteration = 0; iteration < 32; iteration++) |
|||
{ |
|||
Av1ForwardQuantizer.QuantizeLossy( |
|||
coefficients, |
|||
quantized, |
|||
dequantized, |
|||
Av1TransformSize.Size8x8, |
|||
Av1TransformType.DctDct, |
|||
73, |
|||
-1, |
|||
3, |
|||
Av1BitDepth.TenBit); |
|||
} |
|||
|
|||
Assert.Equal(0, GC.GetAllocatedBytesForCurrentThread() - before); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Exercises each transform-scale category, coded 64-point layout, quantizer range, and sample precision.
|
|||
/// </summary>
|
|||
private static void ValidateQuantizer() |
|||
{ |
|||
ReadOnlySpan<Av1TransformSize> transformSizes = |
|||
[ |
|||
Av1TransformSize.Size4x4, |
|||
Av1TransformSize.Size8x8, |
|||
Av1TransformSize.Size16x16, |
|||
Av1TransformSize.Size32x32, |
|||
Av1TransformSize.Size64x16, |
|||
Av1TransformSize.Size64x64, |
|||
]; |
|||
|
|||
ReadOnlySpan<int> quantizerIndices = [1, 73, 173, 255]; |
|||
ReadOnlySpan<Av1BitDepth> bitDepths = [Av1BitDepth.EightBit, Av1BitDepth.TenBit, Av1BitDepth.TwelveBit]; |
|||
|
|||
foreach (Av1TransformSize transformSize in transformSizes) |
|||
{ |
|||
int coefficientCount = transformSize.GetAdjusted().GetSize2d(); |
|||
int[] coefficients = new int[coefficientCount]; |
|||
int[] expectedQuantized = new int[coefficientCount]; |
|||
int[] expectedDequantized = new int[coefficientCount]; |
|||
int[] actualQuantized = new int[coefficientCount]; |
|||
int[] actualDequantized = new int[coefficientCount]; |
|||
|
|||
foreach (int qIndex in quantizerIndices) |
|||
{ |
|||
FillCoefficients(coefficients, qIndex); |
|||
|
|||
foreach (Av1BitDepth bitDepth in bitDepths) |
|||
{ |
|||
ushort expectedEndOfBlock = QuantizeReference( |
|||
coefficients, |
|||
expectedQuantized, |
|||
expectedDequantized, |
|||
transformSize, |
|||
Av1TransformType.DctDct, |
|||
qIndex, |
|||
-1, |
|||
3, |
|||
bitDepth); |
|||
|
|||
ushort actualEndOfBlock = Av1ForwardQuantizer.QuantizeLossy( |
|||
coefficients, |
|||
actualQuantized, |
|||
actualDequantized, |
|||
transformSize, |
|||
Av1TransformType.DctDct, |
|||
qIndex, |
|||
-1, |
|||
3, |
|||
bitDepth); |
|||
|
|||
Assert.Equal(expectedEndOfBlock, actualEndOfBlock); |
|||
Assert.Equal(expectedQuantized, actualQuantized); |
|||
Assert.Equal(expectedDequantized, actualDequantized); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Fills one transform with deterministic signed values spanning threshold, rounding, and clamp behavior.
|
|||
/// </summary>
|
|||
private static void FillCoefficients(Span<int> coefficients, int seed) |
|||
{ |
|||
for (int i = 0; i < coefficients.Length; i++) |
|||
{ |
|||
coefficients[i] = (((i * 7919) + (seed * 313)) % 90001) - 45000; |
|||
} |
|||
|
|||
coefficients[0] = 0; |
|||
coefficients[1] = 1; |
|||
coefficients[2] = -1; |
|||
coefficients[3] = short.MaxValue; |
|||
coefficients[4] = -short.MaxValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Mirrors av1_quantize_fp_no_qmatrix from current libaom without sharing the production traversal.
|
|||
/// </summary>
|
|||
private static ushort QuantizeReference( |
|||
ReadOnlySpan<int> coefficients, |
|||
Span<int> quantizedCoefficients, |
|||
Span<int> dequantizedCoefficients, |
|||
Av1TransformSize transformSize, |
|||
Av1TransformType transformType, |
|||
int qIndex, |
|||
int dcDeltaQ, |
|||
int acDeltaQ, |
|||
Av1BitDepth bitDepth) |
|||
{ |
|||
quantizedCoefficients.Clear(); |
|||
dequantizedCoefficients.Clear(); |
|||
|
|||
int logScale = transformSize.GetScale(); |
|||
int dcDequantizer = Av1QuantizationLookup.GetDcQuant(qIndex, dcDeltaQ, bitDepth); |
|||
int acDequantizer = Av1QuantizationLookup.GetAcQuant(qIndex, acDeltaQ, bitDepth); |
|||
int dcQuantizer = (1 << 16) / dcDequantizer; |
|||
int acQuantizer = (1 << 16) / acDequantizer; |
|||
int dcRounding = RoundPowerOfTwo((64 * dcDequantizer) >> 7, logScale); |
|||
int acRounding = RoundPowerOfTwo((64 * acDequantizer) >> 7, logScale); |
|||
ReadOnlySpan<short> scan = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType).Scan; |
|||
ushort endOfBlock = 0; |
|||
|
|||
for (int scanIndex = 0; scanIndex < scan.Length; scanIndex++) |
|||
{ |
|||
int coefficientIndex = scan[scanIndex]; |
|||
int coefficient = coefficients[coefficientIndex]; |
|||
int coefficientSign = coefficient >> 31; |
|||
long magnitude = ((long)coefficient ^ coefficientSign) - coefficientSign; |
|||
int dequantizer = coefficientIndex == 0 ? dcDequantizer : acDequantizer; |
|||
int quantizer = coefficientIndex == 0 ? dcQuantizer : acQuantizer; |
|||
int rounding = coefficientIndex == 0 ? dcRounding : acRounding; |
|||
int quantizedMagnitude = 0; |
|||
|
|||
if ((magnitude << (1 + logScale)) >= dequantizer) |
|||
{ |
|||
magnitude = Math.Clamp(magnitude + rounding, short.MinValue, short.MaxValue); |
|||
quantizedMagnitude = (int)((magnitude * quantizer) >> (16 - logScale)); |
|||
} |
|||
|
|||
if (quantizedMagnitude != 0) |
|||
{ |
|||
quantizedCoefficients[coefficientIndex] = (quantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
int dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale; |
|||
dequantizedCoefficients[coefficientIndex] = (dequantizedMagnitude ^ coefficientSign) - coefficientSign; |
|||
endOfBlock = (ushort)(scanIndex + 1); |
|||
} |
|||
} |
|||
|
|||
return endOfBlock; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies libaom's positive round-power-of-two operation.
|
|||
/// </summary>
|
|||
private static int RoundPowerOfTwo(int value, int shift) |
|||
=> shift == 0 ? value : (value + (1 << (shift - 1))) >> shift; |
|||
} |
|||
Loading…
Reference in new issue