mirror of https://github.com/SixLabors/ImageSharp
24 changed files with 2871 additions and 178 deletions
@ -0,0 +1,844 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Av1TransferVectorOperators; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <content>
|
|||
/// Provides the SIMD implementations of the H.273 transfer characteristics used by AV1 color conversion.
|
|||
/// </content>
|
|||
internal static partial class Av1TransferFunctions |
|||
{ |
|||
/// <summary>
|
|||
/// Converts four nonlinear signal values to their H.273 linear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The corresponding linear-domain values.</returns>
|
|||
public static Vector128<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value) |
|||
=> ToLinear<Vector128<float>, Vector128Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight nonlinear signal values to their H.273 linear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The corresponding linear-domain values.</returns>
|
|||
public static Vector256<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value) |
|||
=> ToLinear<Vector256<float>, Vector256Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts sixteen nonlinear signal values to their H.273 linear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The corresponding linear-domain values.</returns>
|
|||
public static Vector512<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value) |
|||
=> ToLinear<Vector512<float>, Vector512Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts four linear signal values to their H.273 nonlinear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The corresponding nonlinear-domain values.</returns>
|
|||
public static Vector128<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value) |
|||
=> ToGamma<Vector128<float>, Vector128Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight linear signal values to their H.273 nonlinear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The corresponding nonlinear-domain values.</returns>
|
|||
public static Vector256<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value) |
|||
=> ToGamma<Vector256<float>, Vector256Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts sixteen linear signal values to their H.273 nonlinear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The corresponding nonlinear-domain values.</returns>
|
|||
public static Vector512<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value) |
|||
=> ToGamma<Vector512<float>, Vector512Operator>(transferCharacteristics, value); |
|||
|
|||
/// <summary>
|
|||
/// Converts nonlinear signal values to their H.273 linear-domain values using the selected SIMD width.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The corresponding linear-domain values.</returns>
|
|||
private static TVector ToLinear<TVector, TOperator>(ObuTransferCharacteristics transferCharacteristics, TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
|
|||
switch (transferCharacteristics) |
|||
{ |
|||
case ObuTransferCharacteristics.Bt709: |
|||
case ObuTransferCharacteristics.Bt601: |
|||
case ObuTransferCharacteristics.Bt202010Bit: |
|||
case ObuTransferCharacteristics.Bt202012Bit: |
|||
return ToLinearBt709<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Bt470M: |
|||
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 2.2F); |
|||
case ObuTransferCharacteristics.Bt470BG: |
|||
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 2.8F); |
|||
case ObuTransferCharacteristics.Smpte240: |
|||
return ToLinearSmpte240<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Linear: |
|||
return TOperator.Min(TOperator.Max(value, zero), one); |
|||
case ObuTransferCharacteristics.Log100: |
|||
{ |
|||
// H.273 assigns an interval to zero for logarithmic curves. The scalar midpoint convention is
|
|||
// selected lane-wise after evaluating the positive branch, which keeps the hot path branchless.
|
|||
TVector exponent = TOperator.Multiply(TOperator.Subtract(TOperator.Min(value, one), one), TOperator.Create(2F * 2.302585092994046F)); |
|||
TVector positive = TOperator.Exp(exponent); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), TOperator.Create(0.005F), positive); |
|||
} |
|||
|
|||
case ObuTransferCharacteristics.Log100Sqrt10: |
|||
{ |
|||
TVector exponent = TOperator.Multiply(TOperator.Subtract(TOperator.Min(value, one), one), TOperator.Create(2.5F * 2.302585092994046F)); |
|||
TVector positive = TOperator.Exp(exponent); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), TOperator.Create(0.00158113883F), positive); |
|||
} |
|||
|
|||
case ObuTransferCharacteristics.Iec61966: |
|||
return ToLinearIec61966<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Bt1361: |
|||
return ToLinearBt1361<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Srgb: |
|||
return ToLinearSrgb<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Smpte2084: |
|||
return ToLinearPq<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Smpte428: |
|||
return TOperator.Divide(Power<TVector, TOperator>(TOperator.Max(value, zero), 2.6F), TOperator.Create(Smpte428Scale)); |
|||
case ObuTransferCharacteristics.Hlg: |
|||
return ToLinearHlg<TVector, TOperator>(value); |
|||
default: |
|||
return ToLinearBt709<TVector, TOperator>(value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts linear signal values to their H.273 nonlinear-domain values using the selected SIMD width.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The corresponding nonlinear-domain values.</returns>
|
|||
private static TVector ToGamma<TVector, TOperator>(ObuTransferCharacteristics transferCharacteristics, TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
|
|||
switch (transferCharacteristics) |
|||
{ |
|||
case ObuTransferCharacteristics.Bt709: |
|||
case ObuTransferCharacteristics.Bt601: |
|||
case ObuTransferCharacteristics.Bt202010Bit: |
|||
case ObuTransferCharacteristics.Bt202012Bit: |
|||
return ToGammaBt709<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Bt470M: |
|||
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.2F); |
|||
case ObuTransferCharacteristics.Bt470BG: |
|||
return Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), one), 1F / 2.8F); |
|||
case ObuTransferCharacteristics.Smpte240: |
|||
return ToGammaSmpte240<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Linear: |
|||
return TOperator.Min(TOperator.Max(value, zero), one); |
|||
case ObuTransferCharacteristics.Log100: |
|||
{ |
|||
// Clamp inactive lanes to the threshold before Log. ConditionalSelect does not short-circuit,
|
|||
// so this prevents negative input lanes from contaminating the vector operation with NaN values.
|
|||
TVector threshold = TOperator.Create(0.01F); |
|||
TVector bounded = TOperator.Min(TOperator.Max(value, threshold), one); |
|||
TVector positive = TOperator.Add(one, TOperator.Divide(TOperator.Log(bounded), TOperator.Create(2F * 2.302585092994046F))); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, threshold), zero, positive); |
|||
} |
|||
|
|||
case ObuTransferCharacteristics.Log100Sqrt10: |
|||
{ |
|||
TVector threshold = TOperator.Create(0.00316227766F); |
|||
TVector bounded = TOperator.Min(TOperator.Max(value, threshold), one); |
|||
TVector positive = TOperator.Add(one, TOperator.Divide(TOperator.Log(bounded), TOperator.Create(2.5F * 2.302585092994046F))); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, threshold), zero, positive); |
|||
} |
|||
|
|||
case ObuTransferCharacteristics.Iec61966: |
|||
return ToGammaIec61966<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Bt1361: |
|||
return ToGammaBt1361<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Srgb: |
|||
return ToGammaSrgb<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Smpte2084: |
|||
return ToGammaPq<TVector, TOperator>(value); |
|||
case ObuTransferCharacteristics.Smpte428: |
|||
return Power<TVector, TOperator>(TOperator.Multiply(TOperator.Create(Smpte428Scale), TOperator.Max(value, zero)), 1F / 2.6F); |
|||
case ObuTransferCharacteristics.Hlg: |
|||
return ToGammaHlg<TVector, TOperator>(value); |
|||
default: |
|||
return ToGammaBt709<TVector, TOperator>(value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse BT.709-family opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The linear signal values.</returns>
|
|||
private static TVector ToLinearBt709<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
TVector linear = TOperator.Divide(value, TOperator.Create(4.5F)); |
|||
TVector baseValue = TOperator.Divide(TOperator.Add(value, TOperator.Create(Bt709Alpha - 1F)), TOperator.Create(Bt709Alpha)); |
|||
TVector nonlinear = Power<TVector, TOperator>(TOperator.Max(baseValue, zero), 1F / 0.45F); |
|||
TVector belowOne = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(4.5F * Bt709Beta)), linear, nonlinear); |
|||
|
|||
// The comparisons deliberately mirror the scalar ordering. This preserves the H.273 lower and upper
|
|||
// saturation rules while allowing all lanes to execute without data-dependent branches.
|
|||
TVector bounded = TOperator.ConditionalSelect(TOperator.LessThan(value, one), belowOne, one); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, zero), zero, bounded); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the BT.709-family opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaBt709<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
TVector linear = TOperator.Multiply(value, TOperator.Create(4.5F)); |
|||
TVector nonlinear = TOperator.Subtract( |
|||
TOperator.Multiply(TOperator.Create(Bt709Alpha), Power<TVector, TOperator>(TOperator.Max(value, zero), 0.45F)), |
|||
TOperator.Create(Bt709Alpha - 1F)); |
|||
TVector belowOne = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(Bt709Beta)), linear, nonlinear); |
|||
TVector bounded = TOperator.ConditionalSelect(TOperator.LessThan(value, one), belowOne, one); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, zero), zero, bounded); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse SMPTE ST 240 opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The linear signal values.</returns>
|
|||
private static TVector ToLinearSmpte240<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
TVector linear = TOperator.Divide(value, TOperator.Create(4F)); |
|||
TVector baseValue = TOperator.Divide(TOperator.Add(value, TOperator.Create(Smpte240Alpha - 1F)), TOperator.Create(Smpte240Alpha)); |
|||
TVector nonlinear = Power<TVector, TOperator>(TOperator.Max(baseValue, zero), 1F / 0.45F); |
|||
TVector belowOne = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(4F * Smpte240Beta)), linear, nonlinear); |
|||
TVector bounded = TOperator.ConditionalSelect(TOperator.LessThan(value, one), belowOne, one); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, zero), zero, bounded); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the SMPTE ST 240 opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaSmpte240<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector one = TOperator.Create(1F); |
|||
TVector linear = TOperator.Multiply(value, TOperator.Create(4F)); |
|||
TVector nonlinear = TOperator.Subtract( |
|||
TOperator.Multiply(TOperator.Create(Smpte240Alpha), Power<TVector, TOperator>(TOperator.Max(value, zero), 0.45F)), |
|||
TOperator.Create(Smpte240Alpha - 1F)); |
|||
TVector belowOne = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(Smpte240Beta)), linear, nonlinear); |
|||
TVector bounded = TOperator.ConditionalSelect(TOperator.LessThan(value, one), belowOne, one); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, zero), zero, bounded); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse extended IEC 61966-2-4 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The linear signal values.</returns>
|
|||
private static TVector ToLinearIec61966<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector negativeBase = TOperator.Divide(TOperator.Subtract(value, TOperator.Create(Bt709Alpha - 1F)), TOperator.Create(-Bt709Alpha)); |
|||
TVector negative = TOperator.Negate(Power<TVector, TOperator>(TOperator.Max(negativeBase, TOperator.Create(0F)), 1F / 0.45F)); |
|||
TVector linear = TOperator.Divide(value, TOperator.Create(4.5F)); |
|||
TVector positiveBase = TOperator.Divide(TOperator.Add(value, TOperator.Create(Bt709Alpha - 1F)), TOperator.Create(Bt709Alpha)); |
|||
TVector positive = Power<TVector, TOperator>(TOperator.Max(positiveBase, TOperator.Create(0F)), 1F / 0.45F); |
|||
TVector centerOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(4.5F * Bt709Beta)), linear, positive); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-4.5F * Bt709Beta)), negative, centerOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the extended IEC 61966-2-4 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaIec61966<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector negative = TOperator.Add( |
|||
TOperator.Negate(TOperator.Multiply( |
|||
TOperator.Create(Bt709Alpha), |
|||
Power<TVector, TOperator>(TOperator.Max(TOperator.Negate(value), zero), 0.45F))), |
|||
TOperator.Create(Bt709Alpha - 1F)); |
|||
|
|||
TVector linear = TOperator.Multiply(value, TOperator.Create(4.5F)); |
|||
TVector positive = TOperator.Subtract( |
|||
TOperator.Multiply(TOperator.Create(Bt709Alpha), Power<TVector, TOperator>(TOperator.Max(value, zero), 0.45F)), |
|||
TOperator.Create(Bt709Alpha - 1F)); |
|||
TVector centerOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(Bt709Beta)), linear, positive); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-Bt709Beta)), negative, centerOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse extended BT.1361 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The linear signal values.</returns>
|
|||
private static TVector ToLinearBt1361<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector negativeBase = TOperator.Divide(TOperator.Subtract(value, TOperator.Create(0.02482420670236F)), TOperator.Create(-0.27482420670236F)); |
|||
TVector negative = TOperator.Divide(Power<TVector, TOperator>(TOperator.Max(negativeBase, zero), 1F / 0.45F), TOperator.Create(-4F)); |
|||
TVector negativeOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, zero), negative, ToLinearBt709<TVector, TOperator>(value)); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-0.25F)), TOperator.Create(-0.25F), negativeOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the extended BT.1361 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaBt1361<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector negativePower = Power<TVector, TOperator>(TOperator.Max(TOperator.Multiply(TOperator.Create(-4F), value), zero), 0.45F); |
|||
TVector negative = TOperator.Add(TOperator.Multiply(TOperator.Create(-0.27482420670236F), negativePower), TOperator.Create(0.02482420670236F)); |
|||
TVector negativeOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, zero), negative, ToGammaBt709<TVector, TOperator>(value)); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-0.25F)), TOperator.Create(-0.25F), negativeOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse extended IEC 61966-2-1 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The linear signal values.</returns>
|
|||
private static TVector ToLinearSrgb<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector negativeBase = TOperator.Divide(TOperator.Subtract(value, TOperator.Create(SrgbAlpha - 1F)), TOperator.Create(-SrgbAlpha)); |
|||
TVector negative = TOperator.Negate(Power<TVector, TOperator>(TOperator.Max(negativeBase, zero), 2.4F)); |
|||
TVector linear = TOperator.Divide(value, TOperator.Create(12.92F)); |
|||
TVector positiveBase = TOperator.Divide(TOperator.Add(value, TOperator.Create(SrgbAlpha - 1F)), TOperator.Create(SrgbAlpha)); |
|||
TVector positive = Power<TVector, TOperator>(TOperator.Max(positiveBase, zero), 2.4F); |
|||
TVector centerOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(12.92F * SrgbBeta)), linear, positive); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-12.92F * SrgbBeta)), negative, centerOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the extended IEC 61966-2-1 transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaSrgb<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector negative = TOperator.Add( |
|||
TOperator.Negate(TOperator.Multiply( |
|||
TOperator.Create(SrgbAlpha), |
|||
Power<TVector, TOperator>(TOperator.Max(TOperator.Negate(value), zero), 1F / 2.4F))), |
|||
TOperator.Create(SrgbAlpha - 1F)); |
|||
|
|||
TVector linear = TOperator.Multiply(value, TOperator.Create(12.92F)); |
|||
TVector positive = TOperator.Subtract( |
|||
TOperator.Multiply(TOperator.Create(SrgbAlpha), Power<TVector, TOperator>(TOperator.Max(value, zero), 1F / 2.4F)), |
|||
TOperator.Create(SrgbAlpha - 1F)); |
|||
TVector centerOrPositive = TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(SrgbBeta)), linear, positive); |
|||
return TOperator.ConditionalSelect(TOperator.LessThan(value, TOperator.Create(-SrgbBeta)), negative, centerOrPositive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse SMPTE ST 2084 perceptual-quantizer transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The normalized linear signal values.</returns>
|
|||
private static TVector ToLinearPq<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector nonlinearPower = Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), TOperator.Create(1F)), 1F / PqM); |
|||
TVector numerator = TOperator.Max(TOperator.Subtract(nonlinearPower, TOperator.Create(PqC1)), zero); |
|||
TVector denominator = TOperator.Subtract(TOperator.Create(PqC2), TOperator.Multiply(TOperator.Create(PqC3), nonlinearPower)); |
|||
TVector positive = TOperator.Min(Power<TVector, TOperator>(TOperator.Divide(numerator, denominator), 1F / PqN), TOperator.Create(1F)); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), zero, positive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the SMPTE ST 2084 perceptual-quantizer transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The normalized linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaPq<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector linearPower = Power<TVector, TOperator>(TOperator.Min(TOperator.Max(value, zero), TOperator.Create(1F)), PqN); |
|||
TVector numerator = TOperator.Add(TOperator.Create(PqC1), TOperator.Multiply(TOperator.Create(PqC2), linearPower)); |
|||
TVector denominator = TOperator.Add(TOperator.Create(1F), TOperator.Multiply(TOperator.Create(PqC3), linearPower)); |
|||
TVector positive = TOperator.Min(Power<TVector, TOperator>(TOperator.Divide(numerator, denominator), PqM), TOperator.Create(1F)); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), zero, positive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the inverse HLG opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The normalized linear signal values.</returns>
|
|||
private static TVector ToLinearHlg<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector bounded = TOperator.Min(TOperator.Max(value, zero), TOperator.Create(1F)); |
|||
TVector linear = TOperator.Divide(TOperator.Multiply(bounded, bounded), TOperator.Create(3F)); |
|||
TVector exponent = TOperator.Divide(TOperator.Subtract(bounded, TOperator.Create(HlgC)), TOperator.Create(HlgA)); |
|||
TVector logarithmic = TOperator.Divide(TOperator.Add(TOperator.Exp(exponent), TOperator.Create(HlgB)), TOperator.Create(12F)); |
|||
TVector positive = TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, TOperator.Create(0.5F)), linear, logarithmic); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), zero, positive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the HLG opto-electronic transfer function to a SIMD vector.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The normalized linear signal values.</param>
|
|||
/// <returns>The nonlinear signal values.</returns>
|
|||
private static TVector ToGammaHlg<TVector, TOperator>(TVector value) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
TVector bounded = TOperator.Min(TOperator.Max(value, zero), TOperator.Create(1F)); |
|||
TVector linear = TOperator.Sqrt(TOperator.Multiply(TOperator.Create(3F), bounded)); |
|||
|
|||
// Clamp the logarithm input for inactive lanes. SIMD conditional selection evaluates both branches,
|
|||
// while the scalar definition evaluates Log only above the 1/12 transition.
|
|||
TVector logarithmInput = TOperator.Max( |
|||
TOperator.Subtract(TOperator.Multiply(TOperator.Create(12F), bounded), TOperator.Create(HlgB)), |
|||
TOperator.Create(float.Epsilon)); |
|||
TVector logarithmic = TOperator.Add(TOperator.Multiply(TOperator.Create(HlgA), TOperator.Log(logarithmInput)), TOperator.Create(HlgC)); |
|||
TVector positive = TOperator.ConditionalSelect(TOperator.LessThanOrEqual(bounded, TOperator.Create(1F / 12F)), linear, logarithmic); |
|||
return TOperator.ConditionalSelect(TOperator.LessThanOrEqual(value, zero), zero, positive); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Raises nonnegative SIMD values to a scalar exponent.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="value">The nonnegative base values.</param>
|
|||
/// <param name="exponent">The exponent applied to every lane.</param>
|
|||
/// <returns>The exponentiated values.</returns>
|
|||
private static TVector Power<TVector, TOperator>(TVector value, float exponent) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
// System.Numerics.Tensors does not currently vectorize Pow. Expressing positive powers as Exp(Log(x) * y)
|
|||
// uses the .NET 10 cross-platform vector math kernels and keeps all transfer-function lanes in SIMD.
|
|||
return TOperator.Exp(TOperator.Multiply(TOperator.Log(value), TOperator.Create(exponent))); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the stateless vector-width operators used by the shared H.273 transfer-function formulas.
|
|||
/// </summary>
|
|||
internal static class Av1TransferVectorOperators |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the lane-wise operations required by the shared H.273 SIMD formulas.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
public interface ITransferVectorOperator<TVector> |
|||
where TVector : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a vector whose lanes contain the specified value.
|
|||
/// </summary>
|
|||
/// <param name="value">The value copied to every lane.</param>
|
|||
/// <returns>The created vector.</returns>
|
|||
public static abstract TVector Create(float value); |
|||
|
|||
/// <summary>
|
|||
/// Adds corresponding vector lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The lane-wise sum.</returns>
|
|||
public static abstract TVector Add(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Subtracts corresponding vector lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The lane-wise difference.</returns>
|
|||
public static abstract TVector Subtract(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies corresponding vector lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The lane-wise product.</returns>
|
|||
public static abstract TVector Multiply(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Multiplies corresponding vector lanes and adds an addend using the fastest supported estimate.
|
|||
/// </summary>
|
|||
/// <param name="left">The left multiplication operand.</param>
|
|||
/// <param name="right">The right multiplication operand.</param>
|
|||
/// <param name="addend">The value added to the product.</param>
|
|||
/// <returns>The lane-wise multiply-add result.</returns>
|
|||
public static abstract TVector MultiplyAddEstimate(TVector left, TVector right, TVector addend); |
|||
|
|||
/// <summary>
|
|||
/// Divides corresponding vector lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The dividend.</param>
|
|||
/// <param name="right">The divisor.</param>
|
|||
/// <returns>The lane-wise quotient.</returns>
|
|||
public static abstract TVector Divide(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Negates every vector lane.
|
|||
/// </summary>
|
|||
/// <param name="value">The input vector.</param>
|
|||
/// <returns>The negated vector.</returns>
|
|||
public static abstract TVector Negate(TVector value); |
|||
|
|||
/// <summary>
|
|||
/// Selects the smaller value in each pair of lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The lane-wise minimum.</returns>
|
|||
public static abstract TVector Min(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Selects the larger value in each pair of lanes.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The lane-wise maximum.</returns>
|
|||
public static abstract TVector Max(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Compares whether each left lane is less than its right lane.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The comparison mask.</returns>
|
|||
public static abstract TVector LessThan(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Compares whether each left lane is less than or equal to its right lane.
|
|||
/// </summary>
|
|||
/// <param name="left">The left operand.</param>
|
|||
/// <param name="right">The right operand.</param>
|
|||
/// <returns>The comparison mask.</returns>
|
|||
public static abstract TVector LessThanOrEqual(TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Selects lanes from two vectors according to a comparison mask.
|
|||
/// </summary>
|
|||
/// <param name="condition">The comparison mask.</param>
|
|||
/// <param name="left">The value selected for set mask lanes.</param>
|
|||
/// <param name="right">The value selected for clear mask lanes.</param>
|
|||
/// <returns>The selected lanes.</returns>
|
|||
public static abstract TVector ConditionalSelect(TVector condition, TVector left, TVector right); |
|||
|
|||
/// <summary>
|
|||
/// Computes the natural exponential of every vector lane.
|
|||
/// </summary>
|
|||
/// <param name="value">The input vector.</param>
|
|||
/// <returns>The lane-wise exponential.</returns>
|
|||
public static abstract TVector Exp(TVector value); |
|||
|
|||
/// <summary>
|
|||
/// Computes the natural logarithm of every vector lane.
|
|||
/// </summary>
|
|||
/// <param name="value">The input vector.</param>
|
|||
/// <returns>The lane-wise logarithm.</returns>
|
|||
public static abstract TVector Log(TVector value); |
|||
|
|||
/// <summary>
|
|||
/// Computes the square root of every vector lane.
|
|||
/// </summary>
|
|||
/// <param name="value">The input vector.</param>
|
|||
/// <returns>The lane-wise square root.</returns>
|
|||
public static abstract TVector Sqrt(TVector value); |
|||
|
|||
/// <summary>
|
|||
/// Converts nonlinear signal lanes to their H.273 linear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The nonlinear signal values.</param>
|
|||
/// <returns>The corresponding linear-domain values.</returns>
|
|||
public static abstract TVector ToLinear(ObuTransferCharacteristics transferCharacteristics, TVector value); |
|||
|
|||
/// <summary>
|
|||
/// Converts linear signal lanes to their H.273 nonlinear-domain values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="value">The linear signal values.</param>
|
|||
/// <returns>The corresponding nonlinear-domain values.</returns>
|
|||
public static abstract TVector ToGamma(ObuTransferCharacteristics transferCharacteristics, TVector value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps the shared transfer-function formulas to 128-bit vector operations.
|
|||
/// </summary>
|
|||
public readonly struct Vector128Operator : ITransferVectorOperator<Vector128<float>> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Create(float value) => Vector128.Create(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Add(Vector128<float> left, Vector128<float> right) => left + right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Subtract(Vector128<float> left, Vector128<float> right) => left - right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Multiply(Vector128<float> left, Vector128<float> right) => left * right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> MultiplyAddEstimate(Vector128<float> left, Vector128<float> right, Vector128<float> addend) |
|||
=> Vector128.MultiplyAddEstimate(left, right, addend); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Divide(Vector128<float> left, Vector128<float> right) => left / right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Negate(Vector128<float> value) => -value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Min(Vector128<float> left, Vector128<float> right) => Vector128.Min(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Max(Vector128<float> left, Vector128<float> right) => Vector128.Max(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LessThan(Vector128<float> left, Vector128<float> right) => Vector128.LessThan(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LessThanOrEqual(Vector128<float> left, Vector128<float> right) => Vector128.LessThanOrEqual(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> ConditionalSelect(Vector128<float> condition, Vector128<float> left, Vector128<float> right) |
|||
=> Vector128.ConditionalSelect(condition, left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Exp(Vector128<float> value) => Vector128.Exp(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Log(Vector128<float> value) => Vector128.Log(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> Sqrt(Vector128<float> value) => Vector128.Sqrt(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value) |
|||
=> Av1TransferFunctions.ToLinear(transferCharacteristics, value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector128<float> value) |
|||
=> Av1TransferFunctions.ToGamma(transferCharacteristics, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps the shared transfer-function formulas to 256-bit vector operations.
|
|||
/// </summary>
|
|||
public readonly struct Vector256Operator : ITransferVectorOperator<Vector256<float>> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Create(float value) => Vector256.Create(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Add(Vector256<float> left, Vector256<float> right) => left + right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Subtract(Vector256<float> left, Vector256<float> right) => left - right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Multiply(Vector256<float> left, Vector256<float> right) => left * right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> MultiplyAddEstimate(Vector256<float> left, Vector256<float> right, Vector256<float> addend) |
|||
=> Vector256.MultiplyAddEstimate(left, right, addend); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Divide(Vector256<float> left, Vector256<float> right) => left / right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Negate(Vector256<float> value) => -value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Min(Vector256<float> left, Vector256<float> right) => Vector256.Min(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Max(Vector256<float> left, Vector256<float> right) => Vector256.Max(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LessThan(Vector256<float> left, Vector256<float> right) => Vector256.LessThan(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LessThanOrEqual(Vector256<float> left, Vector256<float> right) => Vector256.LessThanOrEqual(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> ConditionalSelect(Vector256<float> condition, Vector256<float> left, Vector256<float> right) |
|||
=> Vector256.ConditionalSelect(condition, left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Exp(Vector256<float> value) => Vector256.Exp(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Log(Vector256<float> value) => Vector256.Log(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> Sqrt(Vector256<float> value) => Vector256.Sqrt(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value) |
|||
=> Av1TransferFunctions.ToLinear(transferCharacteristics, value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector256<float> value) |
|||
=> Av1TransferFunctions.ToGamma(transferCharacteristics, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps the shared transfer-function formulas to 512-bit vector operations.
|
|||
/// </summary>
|
|||
public readonly struct Vector512Operator : ITransferVectorOperator<Vector512<float>> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Create(float value) => Vector512.Create(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Add(Vector512<float> left, Vector512<float> right) => left + right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Subtract(Vector512<float> left, Vector512<float> right) => left - right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Multiply(Vector512<float> left, Vector512<float> right) => left * right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> MultiplyAddEstimate(Vector512<float> left, Vector512<float> right, Vector512<float> addend) |
|||
=> Vector512.MultiplyAddEstimate(left, right, addend); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Divide(Vector512<float> left, Vector512<float> right) => left / right; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Negate(Vector512<float> value) => -value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Min(Vector512<float> left, Vector512<float> right) => Vector512.Min(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Max(Vector512<float> left, Vector512<float> right) => Vector512.Max(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LessThan(Vector512<float> left, Vector512<float> right) => Vector512.LessThan(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LessThanOrEqual(Vector512<float> left, Vector512<float> right) => Vector512.LessThanOrEqual(left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> ConditionalSelect(Vector512<float> condition, Vector512<float> left, Vector512<float> right) |
|||
=> Vector512.ConditionalSelect(condition, left, right); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Exp(Vector512<float> value) => Vector512.Exp(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Log(Vector512<float> value) => Vector512.Log(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> Sqrt(Vector512<float> value) => Vector512.Sqrt(value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> ToLinear(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value) |
|||
=> Av1TransferFunctions.ToLinear(transferCharacteristics, value); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> ToGamma(ObuTransferCharacteristics transferCharacteristics, Vector512<float> value) |
|||
=> Av1TransferFunctions.ToGamma(transferCharacteristics, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,623 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using static SixLabors.ImageSharp.Formats.Heif.Av1.Av1TransferVectorOperators; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <content>
|
|||
/// Provides the stateless scalar and SIMD operators used by AV1 YUV-to-RGB row conversion.
|
|||
/// </content>
|
|||
internal static partial class Av1YuvConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines one H.273 YUV-to-RGB operation for scalar and SIMD traversal.
|
|||
/// </summary>
|
|||
private interface IYuvToRgbOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operator consumes chroma components.
|
|||
/// </summary>
|
|||
public static abstract bool UsesChroma { get; } |
|||
|
|||
/// <summary>
|
|||
/// Converts one normalized YUV sample to RGB.
|
|||
/// </summary>
|
|||
/// <param name="y">The normalized luma, replaced by red.</param>
|
|||
/// <param name="cb">The normalized blue-difference component, replaced by green.</param>
|
|||
/// <param name="cr">The normalized red-difference component, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts four normalized YUV samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="y">The normalized luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The normalized blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The normalized red-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight normalized YUV samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="y">The normalized luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The normalized blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The normalized red-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts sixteen normalized YUV samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="y">The normalized luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The normalized blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The normalized red-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores the resolved H.273 values shared by every scalar and SIMD lane.
|
|||
/// </summary>
|
|||
private readonly struct YuvToRgbParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YuvToRgbParameters"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="kr">The red luma coefficient.</param>
|
|||
/// <param name="kg">The green luma coefficient.</param>
|
|||
/// <param name="kb">The blue luma coefficient.</param>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="constantLuminanceScales">The constant-luminance chroma scales.</param>
|
|||
/// <param name="lumaBias">The encoded luma bias.</param>
|
|||
/// <param name="lumaScale">The encoded luma range.</param>
|
|||
/// <param name="chromaBias">The encoded chroma midpoint.</param>
|
|||
/// <param name="chromaScale">The encoded chroma range.</param>
|
|||
public YuvToRgbParameters( |
|||
float kr, |
|||
float kg, |
|||
float kb, |
|||
ObuTransferCharacteristics transferCharacteristics, |
|||
in ConstantLuminanceScales constantLuminanceScales, |
|||
float lumaBias, |
|||
float lumaScale, |
|||
float chromaBias, |
|||
float chromaScale) |
|||
{ |
|||
this.Kr = kr; |
|||
this.Kg = kg; |
|||
this.Kb = kb; |
|||
this.TransferCharacteristics = transferCharacteristics; |
|||
this.ConstantLuminanceScales = constantLuminanceScales; |
|||
this.LumaBias = lumaBias; |
|||
this.LumaScale = lumaScale; |
|||
this.ChromaBias = chromaBias; |
|||
this.ChromaScale = chromaScale; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the red luma coefficient.
|
|||
/// </summary>
|
|||
public float Kr { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the green luma coefficient.
|
|||
/// </summary>
|
|||
public float Kg { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue luma coefficient.
|
|||
/// </summary>
|
|||
public float Kb { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the signaled transfer characteristics.
|
|||
/// </summary>
|
|||
public ObuTransferCharacteristics TransferCharacteristics { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the constant-luminance chroma scales.
|
|||
/// </summary>
|
|||
public ConstantLuminanceScales ConstantLuminanceScales { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the encoded luma bias.
|
|||
/// </summary>
|
|||
public float LumaBias { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the encoded luma range.
|
|||
/// </summary>
|
|||
public float LumaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the encoded chroma midpoint.
|
|||
/// </summary>
|
|||
public float ChromaBias { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the encoded chroma range.
|
|||
/// </summary>
|
|||
public float ChromaScale { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replicates luma into all three RGB components for monochrome input.
|
|||
/// </summary>
|
|||
private readonly struct MonochromeOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
cb = y; |
|||
cr = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
cb = y; |
|||
cr = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
cb = y; |
|||
cr = y; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
cb = y; |
|||
cr = y; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts coefficient-based YCbCr signals to RGB.
|
|||
/// </summary>
|
|||
private readonly struct CoefficientsOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
float r = y + (2F * (1F - parameters.Kr) * cr); |
|||
float g = y - (2F * ((parameters.Kr * (1F - parameters.Kr) * cr) + (parameters.Kb * (1F - parameters.Kb) * cb)) / parameters.Kg); |
|||
float b = y + (2F * (1F - parameters.Kb) * cb); |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertCoefficients<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertCoefficients<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertCoefficients<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr, in parameters); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps identity-coded G, B, and R planes to RGB.
|
|||
/// </summary>
|
|||
private readonly struct IdentityOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
float r = (((cr * parameters.ChromaScale) + parameters.ChromaBias) - parameters.LumaBias) / parameters.LumaScale; |
|||
float b = (((cb * parameters.ChromaScale) + parameters.ChromaBias) - parameters.LumaBias) / parameters.LumaScale; |
|||
cb = y; |
|||
y = r; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertIdentity<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertIdentity<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertIdentity<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr, in parameters); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts YCgCo signals to RGB.
|
|||
/// </summary>
|
|||
private readonly struct YCgCoOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
float temporary = y - cb; |
|||
float r = temporary + cr; |
|||
float g = y + cb; |
|||
float b = temporary - cr; |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertYCgCo<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertYCgCo<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertYCgCo<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts SMPTE ST 2085 YDzDx signals to RGB.
|
|||
/// </summary>
|
|||
private readonly struct Smpte2085Operator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
float g = y; |
|||
float b = ((2F * cb) + y) / 0.986566F; |
|||
float r = (2F * cr) + (0.991902F * y); |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertSmpte2085<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertSmpte2085<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertSmpte2085<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts H.273 constant-luminance signals to RGB.
|
|||
/// </summary>
|
|||
private readonly struct ConstantLuminanceOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
{ |
|||
ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
float nonlinearBlue = y + (2F * (cb <= 0F ? scales.NegativeBlue : scales.PositiveBlue) * cb); |
|||
float nonlinearRed = y + (2F * (cr <= 0F ? scales.NegativeRed : scales.PositiveRed) * cr); |
|||
float linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); |
|||
float linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); |
|||
float linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); |
|||
float linearGreen = (linearY - (parameters.Kr * linearRed) - (parameters.Kb * linearBlue)) / parameters.Kg; |
|||
y = nonlinearRed; |
|||
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = nonlinearBlue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertConstantLuminance<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertConstantLuminance<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr, in parameters); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertConstantLuminance<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr, in parameters); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the PQ-family ICtCp matrix to RGB.
|
|||
/// </summary>
|
|||
private readonly struct ICtCpOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCpScalar(ref y, ref cb, ref cr, in parameters, false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr, in parameters, false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr, in parameters, false); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr, in parameters, false); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts the HLG-specific ICtCp matrix to RGB.
|
|||
/// </summary>
|
|||
private readonly struct ICtCpHlgOperator : IYuvToRgbOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool UsesChroma => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCpScalar(ref y, ref cb, ref cr, in parameters, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector128<float>, Vector128Operator>(ref y, ref cb, ref cr, in parameters, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector256<float>, Vector256Operator>(ref y, ref cb, ref cr, in parameters, true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public static void Convert(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in YuvToRgbParameters parameters) |
|||
=> YuvToRgbMath.ConvertICtCp<Vector512<float>, Vector512Operator>(ref y, ref cb, ref cr, in parameters, true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the shared vector-width-independent arithmetic used by the color operators.
|
|||
/// </summary>
|
|||
private static class YuvToRgbMath |
|||
{ |
|||
/// <summary>
|
|||
/// Converts coefficient-based YCbCr SIMD lanes to RGB.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The red-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static void ConvertCoefficients<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr, in YuvToRgbParameters parameters) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector r = TOperator.MultiplyAddEstimate(TOperator.Create(2F * (1F - parameters.Kr)), cr, y); |
|||
TVector greenDifference = TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(parameters.Kr * (1F - parameters.Kr)), |
|||
cr, |
|||
TOperator.Multiply(TOperator.Create(parameters.Kb * (1F - parameters.Kb)), cb)); |
|||
|
|||
TVector g = TOperator.Subtract(y, TOperator.Multiply(TOperator.Create(2F / parameters.Kg), greenDifference)); |
|||
TVector b = TOperator.MultiplyAddEstimate(TOperator.Create(2F * (1F - parameters.Kb)), cb, y); |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps identity-coded SIMD lanes to RGB.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The green lanes, replaced by red.</param>
|
|||
/// <param name="cb">The normalized blue plane, replaced by green.</param>
|
|||
/// <param name="cr">The normalized red plane, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static void ConvertIdentity<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr, in YuvToRgbParameters parameters) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector lumaBias = TOperator.Create(parameters.LumaBias); |
|||
TVector inverseLumaScale = TOperator.Create(1F / parameters.LumaScale); |
|||
TVector chromaScale = TOperator.Create(parameters.ChromaScale); |
|||
TVector chromaBias = TOperator.Create(parameters.ChromaBias); |
|||
TVector r = TOperator.Multiply(TOperator.Subtract(TOperator.MultiplyAddEstimate(cr, chromaScale, chromaBias), lumaBias), inverseLumaScale); |
|||
TVector b = TOperator.Multiply(TOperator.Subtract(TOperator.MultiplyAddEstimate(cb, chromaScale, chromaBias), lumaBias), inverseLumaScale); |
|||
cb = y; |
|||
y = r; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts YCgCo SIMD lanes to RGB.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The green-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The orange-difference lanes, replaced by blue.</param>
|
|||
public static void ConvertYCgCo<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector temporary = TOperator.Subtract(y, cb); |
|||
TVector r = TOperator.Add(temporary, cr); |
|||
TVector g = TOperator.Add(y, cb); |
|||
TVector b = TOperator.Subtract(temporary, cr); |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts SMPTE ST 2085 SIMD lanes to RGB.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The red-difference lanes, replaced by blue.</param>
|
|||
public static void ConvertSmpte2085<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector g = y; |
|||
TVector b = TOperator.Multiply(TOperator.MultiplyAddEstimate(TOperator.Create(2F), cb, y), TOperator.Create(1F / 0.986566F)); |
|||
TVector r = TOperator.MultiplyAddEstimate(TOperator.Create(2F), cr, TOperator.Multiply(TOperator.Create(0.991902F), y)); |
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts constant-luminance SIMD lanes to RGB.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The nonlinear luma lanes, replaced by red.</param>
|
|||
/// <param name="cb">The blue-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The red-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static void ConvertConstantLuminance<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr, in YuvToRgbParameters parameters) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
TVector zero = TOperator.Create(0F); |
|||
ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
TVector blueScale = TOperator.ConditionalSelect( |
|||
TOperator.LessThanOrEqual(cb, zero), |
|||
TOperator.Create(scales.NegativeBlue), |
|||
TOperator.Create(scales.PositiveBlue)); |
|||
|
|||
TVector redScale = TOperator.ConditionalSelect( |
|||
TOperator.LessThanOrEqual(cr, zero), |
|||
TOperator.Create(scales.NegativeRed), |
|||
TOperator.Create(scales.PositiveRed)); |
|||
|
|||
TVector nonlinearBlue = TOperator.MultiplyAddEstimate(TOperator.Multiply(TOperator.Create(2F), blueScale), cb, y); |
|||
TVector nonlinearRed = TOperator.MultiplyAddEstimate(TOperator.Multiply(TOperator.Create(2F), redScale), cr, y); |
|||
TVector linearY = TOperator.ToLinear(parameters.TransferCharacteristics, y); |
|||
TVector linearBlue = TOperator.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); |
|||
TVector linearRed = TOperator.ToLinear(parameters.TransferCharacteristics, nonlinearRed); |
|||
TVector redAndBlue = TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(parameters.Kr), |
|||
linearRed, |
|||
TOperator.Multiply(TOperator.Create(parameters.Kb), linearBlue)); |
|||
|
|||
TVector linearGreen = TOperator.Divide(TOperator.Subtract(linearY, redAndBlue), TOperator.Create(parameters.Kg)); |
|||
y = nonlinearRed; |
|||
cb = TOperator.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = nonlinearBlue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one ICtCp sample to RGB using the selected inverse matrix.
|
|||
/// </summary>
|
|||
/// <param name="y">The intensity, replaced by red.</param>
|
|||
/// <param name="cb">The tritan-difference component, replaced by green.</param>
|
|||
/// <param name="cr">The protan-difference component, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="isHlg">Whether to use the HLG-specific inverse ICtCp matrix.</param>
|
|||
public static void ConvertICtCpScalar(ref float y, ref float cb, ref float cr, in YuvToRgbParameters parameters, bool isHlg) |
|||
{ |
|||
float nonlinearL = isHlg |
|||
? y + (0.015718580108730413F * cb) + (0.2095810681164055F * cr) |
|||
: y + (0.008609037037932756F * cb) + (0.11102962500302596F * cr); |
|||
|
|||
float nonlinearM = isHlg |
|||
? y - (0.015718580108730413F * cb) - (0.2095810681164055F * cr) |
|||
: y - (0.008609037037932756F * cb) - (0.11102962500302596F * cr); |
|||
|
|||
float nonlinearS = isHlg |
|||
? y + (1.0212710798422342F * cb) - (0.6052744909924315F * cr) |
|||
: y + (0.5600313357106791F * cb) - (0.32062717498731885F * cr); |
|||
|
|||
float linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
float linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
float linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
float linearRed = (3.4366066943330784F * linearL) - (2.50645211865627F * linearM) + (0.06984542432319148F * linearS); |
|||
float linearGreen = (-0.7913295555989287F * linearL) + (1.9836004517922907F * linearM) - (0.192270896193362F * linearS); |
|||
float linearBlue = (-0.025949899690592672F * linearL) - (0.09891371471172644F * linearM) + (1.1248636144023192F * linearS); |
|||
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts ICtCp SIMD lanes to RGB using the selected inverse matrix.
|
|||
/// </summary>
|
|||
/// <typeparam name="TVector">The SIMD vector type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operations for the SIMD vector type.</typeparam>
|
|||
/// <param name="y">The intensity lanes, replaced by red.</param>
|
|||
/// <param name="cb">The tritan-difference lanes, replaced by green.</param>
|
|||
/// <param name="cr">The protan-difference lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="isHlg">Whether to use the HLG-specific inverse ICtCp matrix.</param>
|
|||
public static void ConvertICtCp<TVector, TOperator>(ref TVector y, ref TVector cb, ref TVector cr, in YuvToRgbParameters parameters, bool isHlg) |
|||
where TVector : struct |
|||
where TOperator : struct, ITransferVectorOperator<TVector> |
|||
{ |
|||
float cbToL = isHlg ? 0.015718580108730413F : 0.008609037037932756F; |
|||
float crToL = isHlg ? 0.2095810681164055F : 0.11102962500302596F; |
|||
float cbToS = isHlg ? 1.0212710798422342F : 0.5600313357106791F; |
|||
float crToS = isHlg ? -0.6052744909924315F : -0.32062717498731885F; |
|||
TVector cbContribution = TOperator.Multiply(TOperator.Create(cbToL), cb); |
|||
TVector crContribution = TOperator.Multiply(TOperator.Create(crToL), cr); |
|||
TVector nonlinearL = TOperator.Add(TOperator.Add(y, cbContribution), crContribution); |
|||
TVector nonlinearM = TOperator.Subtract(TOperator.Subtract(y, cbContribution), crContribution); |
|||
TVector nonlinearS = TOperator.MultiplyAddEstimate(TOperator.Create(crToS), cr, TOperator.MultiplyAddEstimate(TOperator.Create(cbToS), cb, y)); |
|||
TVector linearL = TOperator.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
TVector linearM = TOperator.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
TVector linearS = TOperator.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
TVector linearRed = TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(0.06984542432319148F), |
|||
linearS, |
|||
TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(-2.50645211865627F), |
|||
linearM, |
|||
TOperator.Multiply(TOperator.Create(3.4366066943330784F), linearL))); |
|||
|
|||
TVector linearGreen = TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(-0.192270896193362F), |
|||
linearS, |
|||
TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(1.9836004517922907F), |
|||
linearM, |
|||
TOperator.Multiply(TOperator.Create(-0.7913295555989287F), linearL))); |
|||
|
|||
TVector linearBlue = TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(1.1248636144023192F), |
|||
linearS, |
|||
TOperator.MultiplyAddEstimate( |
|||
TOperator.Create(-0.09891371471172644F), |
|||
linearM, |
|||
TOperator.Multiply(TOperator.Create(-0.025949899690592672F), linearL))); |
|||
|
|||
y = TOperator.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
cb = TOperator.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = TOperator.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.InteropServices; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <content>
|
|||
/// Provides the pooled parallel row operation used by AV1 YUV-to-RGB decoding.
|
|||
/// </content>
|
|||
internal static partial class Av1YuvConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Converts one reconstructed AV1 row using worker-owned pooled component, chroma, and packed-pixel storage.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
|
|||
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
|
|||
/// <typeparam name="TLoader">The SIMD widening operations for the sample type.</typeparam>
|
|||
private readonly struct YuvToRgbRowOperation<TPixel, TSample, TLoader> : IRowOperation<float> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TSample : unmanaged |
|||
where TLoader : struct, ISampleLoader<TSample> |
|||
{ |
|||
private readonly Configuration configuration; |
|||
private readonly Av1FrameBuffer<byte> frameBuffer; |
|||
private readonly ImageFrame<TPixel> image; |
|||
private readonly Buffer2DRegion<byte> yPlane; |
|||
private readonly Buffer2DRegion<byte> uPlane; |
|||
private readonly Buffer2DRegion<byte> vPlane; |
|||
private readonly YuvToRgbParameters parameters; |
|||
private readonly ConversionMode mode; |
|||
private readonly ObuChromoSamplePosition chromaSamplePosition; |
|||
private readonly bool isMonochrome; |
|||
private readonly int subX; |
|||
private readonly int subY; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YuvToRgbRowOperation{TPixel, TSample, TLoader}"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration used for packed-pixel conversion.</param>
|
|||
/// <param name="frameBuffer">The reconstructed AV1 frame.</param>
|
|||
/// <param name="image">The destination image frame.</param>
|
|||
/// <param name="mode">The resolved H.273 conversion mode.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public YuvToRgbRowOperation( |
|||
Configuration configuration, |
|||
Av1FrameBuffer<byte> frameBuffer, |
|||
ImageFrame<TPixel> image, |
|||
ConversionMode mode, |
|||
in YuvToRgbParameters parameters) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.frameBuffer = frameBuffer; |
|||
this.image = image; |
|||
this.mode = mode; |
|||
this.parameters = parameters; |
|||
this.isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|||
this.subX = frameBuffer.ColorConfig.SubSamplingX ? 1 : 0; |
|||
this.subY = frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
|||
this.chromaSamplePosition = frameBuffer.ColorConfig.ChromaSamplePosition; |
|||
this.yPlane = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0); |
|||
this.uPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.U, this.subX, this.subY); |
|||
this.vPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.V, this.subX, this.subY); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public int GetRequiredBufferLength(Rectangle bounds) |
|||
{ |
|||
// Three float component rows are converted in place. Color input adds two reusable chroma scratch
|
|||
// rows, and the final one or two float-sized slots per pixel back an Rgba32 or Rgba64 staging row.
|
|||
int rowCount = this.isMonochrome ? 3 : 5; |
|||
int packedRowCount = typeof(TSample) == typeof(byte) ? 1 : 2; |
|||
return bounds.Width * (rowCount + packedRowCount); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Invoke(int y, Span<float> span) |
|||
{ |
|||
int width = this.image.Width; |
|||
Span<float> red = span[..width]; |
|||
Span<float> green = span.Slice(width, width); |
|||
Span<float> blue = span.Slice(width * 2, width); |
|||
|
|||
ReadOnlySpan<TSample> ySource; |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
ySource = MemoryMarshal.Cast<byte, TSample>(this.yPlane.DangerousGetRowSpan(y)); |
|||
} |
|||
else |
|||
{ |
|||
ySource = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, y, 0, 0)); |
|||
} |
|||
|
|||
ConvertSamplesToFloat<TSample, TLoader>(ySource, red); |
|||
|
|||
int packedOffset = width * 3; |
|||
if (!this.isMonochrome) |
|||
{ |
|||
GetChromaCoordinates( |
|||
y, |
|||
this.subY, |
|||
this.subY != 0 && this.chromaSamplePosition != ObuChromoSamplePosition.Colocated, |
|||
this.uPlane.Height - 1, |
|||
out int y0, |
|||
out int y1, |
|||
out int y1Weight); |
|||
|
|||
ReadOnlySpan<TSample> uRow0; |
|||
ReadOnlySpan<TSample> uRow1; |
|||
ReadOnlySpan<TSample> vRow0; |
|||
ReadOnlySpan<TSample> vRow1; |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
uRow0 = MemoryMarshal.Cast<byte, TSample>(this.uPlane.DangerousGetRowSpan(y0)); |
|||
uRow1 = MemoryMarshal.Cast<byte, TSample>(this.uPlane.DangerousGetRowSpan(y1)); |
|||
vRow0 = MemoryMarshal.Cast<byte, TSample>(this.vPlane.DangerousGetRowSpan(y0)); |
|||
vRow1 = MemoryMarshal.Cast<byte, TSample>(this.vPlane.DangerousGetRowSpan(y1)); |
|||
} |
|||
else |
|||
{ |
|||
uRow0 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.U, y0, this.subX, this.subY)); |
|||
uRow1 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.U, y1, this.subX, this.subY)); |
|||
vRow0 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, y0, this.subX, this.subY)); |
|||
vRow1 = MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, y1, this.subX, this.subY)); |
|||
} |
|||
|
|||
Span<float> scratch0 = span.Slice(width * 3, width); |
|||
Span<float> scratch1 = span.Slice(width * 4, width); |
|||
bool isCenteredX = this.subX != 0 && (this.subY == 0 || this.chromaSamplePosition == ObuChromoSamplePosition.Unknown); |
|||
ReconstructChromaRow<TSample, TLoader>(uRow0, uRow1, y1Weight, this.subX, isCenteredX, green, scratch0, scratch1); |
|||
ReconstructChromaRow<TSample, TLoader>(vRow0, vRow1, y1Weight, this.subX, isCenteredX, blue, scratch0, scratch1); |
|||
packedOffset = width * 5; |
|||
} |
|||
|
|||
ConvertYuvToRgbRow(red, green, blue, this.isMonochrome, this.mode, in this.parameters); |
|||
Span<float> packedStorage = span[packedOffset..]; |
|||
Span<TPixel> destination = this.image.PixelBuffer.DangerousGetRowSpan(y); |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
Span<Rgba32> packed = MemoryMarshal.Cast<float, Rgba32>(packedStorage)[..width]; |
|||
PackRgba32(red, green, blue, packed); |
|||
PixelOperations<TPixel>.Instance.FromRgba32(this.configuration, packed, destination); |
|||
} |
|||
else |
|||
{ |
|||
Span<Rgba64> packed = MemoryMarshal.Cast<float, Rgba64>(packedStorage)[..width]; |
|||
PackRgba64(red, green, blue, packed); |
|||
PixelOperations<TPixel>.Instance.FromRgba64(this.configuration, packed, destination); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,790 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Common.Helpers; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <content>
|
|||
/// Provides SIMD sample widening, chroma reconstruction, color traversal, and packed output for AV1 decoding.
|
|||
/// </content>
|
|||
internal static partial class Av1YuvConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the SIMD widening operations for one reconstructed AV1 sample type.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
|
|||
private interface ISampleLoader<TSample> |
|||
where TSample : unmanaged |
|||
{ |
|||
/// <summary>
|
|||
/// Loads and widens four samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector128<float> LoadVector128(ref TSample source); |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens eight samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector256<float> LoadVector256(ref TSample source); |
|||
|
|||
/// <summary>
|
|||
/// Loads and widens sixteen samples to single-precision lanes.
|
|||
/// </summary>
|
|||
/// <param name="source">The first source sample.</param>
|
|||
/// <returns>The widened samples.</returns>
|
|||
public static abstract Vector512<float> LoadVector512(ref TSample source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens reconstructed integer samples into a pooled float component row.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
|
|||
/// <typeparam name="TLoader">The widening operations for the sample type.</typeparam>
|
|||
/// <param name="source">The reconstructed samples.</param>
|
|||
/// <param name="destination">The destination component row.</param>
|
|||
private static void ConvertSamplesToFloat<TSample, TLoader>(ReadOnlySpan<TSample> source, Span<float> destination) |
|||
where TSample : unmanaged |
|||
where TLoader : struct, ISampleLoader<TSample> |
|||
{ |
|||
ref TSample sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref float destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int length = destination.Length; |
|||
int i = 0; |
|||
|
|||
// Descending vector widths match the JPEG color-converter traversal. A wide-capable CPU processes
|
|||
// complete wide batches first while short and irregular rows continue through narrower SIMD tails.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> samples = TLoader.LoadVector512(ref Unsafe.Add(ref sourceBase, i)); |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref destinationBase, i)) = samples; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> samples = TLoader.LoadVector256(ref Unsafe.Add(ref sourceBase, i)); |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref destinationBase, i)) = samples; |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> samples = TLoader.LoadVector128(ref Unsafe.Add(ref sourceBase, i)); |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref destinationBase, i)) = samples; |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = GetSample(source, i); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reconstructs one full-width chroma row using the signaled vertical and horizontal sample positions.
|
|||
/// </summary>
|
|||
/// <typeparam name="TSample">The reconstructed sample type.</typeparam>
|
|||
/// <typeparam name="TLoader">The widening operations for the sample type.</typeparam>
|
|||
/// <param name="row0">The upper chroma row.</param>
|
|||
/// <param name="row1">The lower chroma row.</param>
|
|||
/// <param name="y1Weight">The lower-row weight with a denominator of four.</param>
|
|||
/// <param name="subX">The horizontal chroma subsampling shift.</param>
|
|||
/// <param name="isCenteredX">Whether horizontally subsampled chroma is centered between luma samples.</param>
|
|||
/// <param name="destination">The reconstructed full-width chroma row.</param>
|
|||
/// <param name="scratch0">The first pooled chroma scratch row.</param>
|
|||
/// <param name="scratch1">The second pooled chroma scratch row.</param>
|
|||
private static void ReconstructChromaRow<TSample, TLoader>( |
|||
ReadOnlySpan<TSample> row0, |
|||
ReadOnlySpan<TSample> row1, |
|||
int y1Weight, |
|||
int subX, |
|||
bool isCenteredX, |
|||
Span<float> destination, |
|||
Span<float> scratch0, |
|||
Span<float> scratch1) |
|||
where TSample : unmanaged |
|||
where TLoader : struct, ISampleLoader<TSample> |
|||
{ |
|||
int sourceLength = subX == 0 ? destination.Length : (destination.Length + 1) >> 1; |
|||
Span<float> top = scratch0[..sourceLength]; |
|||
ConvertSamplesToFloat<TSample, TLoader>(row0, top); |
|||
|
|||
if (y1Weight != 0) |
|||
{ |
|||
Span<float> bottom = scratch1[..sourceLength]; |
|||
ConvertSamplesToFloat<TSample, TLoader>(row1, bottom); |
|||
InterpolateChromaRows(top, bottom, y1Weight); |
|||
} |
|||
|
|||
if (subX == 0) |
|||
{ |
|||
top.CopyTo(destination); |
|||
return; |
|||
} |
|||
|
|||
UpsampleChromaHorizontal(top, destination, isCenteredX); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interpolates two chroma rows in place using AV1 quarter-sample weights.
|
|||
/// </summary>
|
|||
/// <param name="top">The upper row, replaced by the interpolated values.</param>
|
|||
/// <param name="bottom">The lower row.</param>
|
|||
/// <param name="bottomWeight">The lower-row weight with a denominator of four.</param>
|
|||
private static void InterpolateChromaRows(Span<float> top, ReadOnlySpan<float> bottom, int bottomWeight) |
|||
{ |
|||
ref float topBase = ref MemoryMarshal.GetReference(top); |
|||
ref float bottomBase = ref MemoryMarshal.GetReference(bottom); |
|||
int length = top.Length; |
|||
int i = 0; |
|||
float topWeight = 4 - bottomWeight; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<float> topWeightVector = Vector512.Create(topWeight); |
|||
Vector512<float> bottomWeightVector = Vector512.Create((float)bottomWeight); |
|||
Vector512<float> scale = Vector512.Create(0.25F); |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
ref Vector512<float> topVector = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref topBase, i)); |
|||
Vector512<float> bottomVector = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref bottomBase, i)); |
|||
topVector = Vector512.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<float> topWeightVector = Vector256.Create(topWeight); |
|||
Vector256<float> bottomWeightVector = Vector256.Create((float)bottomWeight); |
|||
Vector256<float> scale = Vector256.Create(0.25F); |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
ref Vector256<float> topVector = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref topBase, i)); |
|||
Vector256<float> bottomVector = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref bottomBase, i)); |
|||
topVector = Vector256.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<float> topWeightVector = Vector128.Create(topWeight); |
|||
Vector128<float> bottomWeightVector = Vector128.Create((float)bottomWeight); |
|||
Vector128<float> scale = Vector128.Create(0.25F); |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
ref Vector128<float> topVector = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref topBase, i)); |
|||
Vector128<float> bottomVector = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref bottomBase, i)); |
|||
topVector = Vector128.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref topBase, i) = ((Unsafe.Add(ref topBase, i) * topWeight) + (Unsafe.Add(ref bottomBase, i) * bottomWeight)) * 0.25F; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Expands horizontally subsampled chroma to luma width using the AV1 sample-position rules.
|
|||
/// </summary>
|
|||
/// <param name="source">The subsampled chroma values.</param>
|
|||
/// <param name="destination">The full-width chroma values.</param>
|
|||
/// <param name="isCentered">Whether chroma lies between neighboring luma samples.</param>
|
|||
private static void UpsampleChromaHorizontal(ReadOnlySpan<float> source, Span<float> destination, bool isCentered) |
|||
{ |
|||
ref float sourceBase = ref MemoryMarshal.GetReference(source); |
|||
ref float destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int sourceLength = source.Length; |
|||
int i = 0; |
|||
|
|||
if (isCentered) |
|||
{ |
|||
// The first centered pair extends the left edge. Interior vectors can then read one real neighbor
|
|||
// on each side and use the exact [1,3]/4 and [3,1]/4 AV1 interpolation weights.
|
|||
StoreChromaPair(ref destinationBase, 0, source[0], ((3F * source[0]) + source[Math.Min(1, sourceLength - 1)]) * 0.25F, destination.Length); |
|||
i = 1; |
|||
} |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorBeforeEnd = sourceLength - Vector512<float>.Count - 1; |
|||
Vector512<float> quarter = Vector512.Create(0.25F); |
|||
Vector512<float> half = Vector512.Create(0.5F); |
|||
Vector512<float> three = Vector512.Create(3F); |
|||
for (; i <= oneVectorBeforeEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> center = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector512<float> next = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref sourceBase, i + 1)); |
|||
Vector512<float> even; |
|||
Vector512<float> odd; |
|||
if (isCentered) |
|||
{ |
|||
Vector512<float> previous = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref sourceBase, i - 1)); |
|||
even = Vector512.MultiplyAddEstimate(three, center, previous) * quarter; |
|||
odd = Vector512.MultiplyAddEstimate(three, center, next) * quarter; |
|||
} |
|||
else |
|||
{ |
|||
even = center; |
|||
odd = (center + next) * half; |
|||
} |
|||
|
|||
// Vector512 has no cross-platform unpack helper. The interpolation remains 512-bit; four
|
|||
// established Vector128 unpack operations only transpose the final even/odd lanes for storage.
|
|||
StoreInterleavedChroma(even.GetLower().GetLower(), odd.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, i * 2)); |
|||
StoreInterleavedChroma(even.GetLower().GetUpper(), odd.GetLower().GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 8)); |
|||
StoreInterleavedChroma(even.GetUpper().GetLower(), odd.GetUpper().GetLower(), ref Unsafe.Add(ref destinationBase, (i * 2) + 16)); |
|||
StoreInterleavedChroma(even.GetUpper().GetUpper(), odd.GetUpper().GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 24)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorBeforeEnd = sourceLength - Vector256<float>.Count - 1; |
|||
Vector256<float> quarter = Vector256.Create(0.25F); |
|||
Vector256<float> half = Vector256.Create(0.5F); |
|||
Vector256<float> three = Vector256.Create(3F); |
|||
for (; i <= oneVectorBeforeEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> center = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector256<float> next = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref sourceBase, i + 1)); |
|||
Vector256<float> even; |
|||
Vector256<float> odd; |
|||
if (isCentered) |
|||
{ |
|||
Vector256<float> previous = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref sourceBase, i - 1)); |
|||
even = Vector256.MultiplyAddEstimate(three, center, previous) * quarter; |
|||
odd = Vector256.MultiplyAddEstimate(three, center, next) * quarter; |
|||
} |
|||
else |
|||
{ |
|||
even = center; |
|||
odd = (center + next) * half; |
|||
} |
|||
|
|||
StoreInterleavedChroma(even.GetLower(), odd.GetLower(), ref Unsafe.Add(ref destinationBase, i * 2)); |
|||
StoreInterleavedChroma(even.GetUpper(), odd.GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 8)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorBeforeEnd = sourceLength - Vector128<float>.Count - 1; |
|||
Vector128<float> quarter = Vector128.Create(0.25F); |
|||
Vector128<float> half = Vector128.Create(0.5F); |
|||
Vector128<float> three = Vector128.Create(3F); |
|||
for (; i <= oneVectorBeforeEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> center = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref sourceBase, i)); |
|||
Vector128<float> next = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref sourceBase, i + 1)); |
|||
Vector128<float> even; |
|||
Vector128<float> odd; |
|||
if (isCentered) |
|||
{ |
|||
Vector128<float> previous = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref sourceBase, i - 1)); |
|||
even = Vector128.MultiplyAddEstimate(three, center, previous) * quarter; |
|||
odd = Vector128.MultiplyAddEstimate(three, center, next) * quarter; |
|||
} |
|||
else |
|||
{ |
|||
even = center; |
|||
odd = (center + next) * half; |
|||
} |
|||
|
|||
StoreInterleavedChroma(even, odd, ref Unsafe.Add(ref destinationBase, i * 2)); |
|||
} |
|||
} |
|||
|
|||
for (; i < sourceLength; i++) |
|||
{ |
|||
float center = source[i]; |
|||
float next = source[Math.Min(i + 1, sourceLength - 1)]; |
|||
float even = isCentered ? (source[Math.Max(i - 1, 0)] + (3F * center)) * 0.25F : center; |
|||
float odd = isCentered ? ((3F * center) + next) * 0.25F : (center + next) * 0.5F; |
|||
StoreChromaPair(ref destinationBase, i * 2, even, odd, destination.Length); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores four even chroma lanes interleaved with their four odd lanes.
|
|||
/// </summary>
|
|||
/// <param name="even">The even luma-coordinate values.</param>
|
|||
/// <param name="odd">The odd luma-coordinate values.</param>
|
|||
/// <param name="destination">The first destination value.</param>
|
|||
private static void StoreInterleavedChroma(Vector128<float> even, Vector128<float> odd, ref float destination) |
|||
{ |
|||
Vector128<float> lower = Vector128_.UnpackLow(even.AsInt32(), odd.AsInt32()).AsSingle(); |
|||
Vector128<float> upper = Vector128_.UnpackHigh(even.AsInt32(), odd.AsInt32()).AsSingle(); |
|||
Unsafe.As<float, Vector128<float>>(ref destination) = lower; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref destination, Vector128<float>.Count)) = upper; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Stores one reconstructed chroma pair without writing beyond an odd-width destination row.
|
|||
/// </summary>
|
|||
/// <param name="destination">The first destination value.</param>
|
|||
/// <param name="index">The even destination index.</param>
|
|||
/// <param name="even">The even luma-coordinate value.</param>
|
|||
/// <param name="odd">The odd luma-coordinate value.</param>
|
|||
/// <param name="length">The destination length.</param>
|
|||
private static void StoreChromaPair(ref float destination, int index, float even, float odd, int length) |
|||
{ |
|||
Unsafe.Add(ref destination, index) = even; |
|||
if (index + 1 < length) |
|||
{ |
|||
Unsafe.Add(ref destination, index + 1) = odd; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dispatches one normalized component row to its matrix-specific scalar and SIMD operator.
|
|||
/// </summary>
|
|||
/// <param name="red">The luma row, replaced by red.</param>
|
|||
/// <param name="green">The blue-difference row, replaced by green.</param>
|
|||
/// <param name="blue">The red-difference row, replaced by blue.</param>
|
|||
/// <param name="isMonochrome">Whether the frame contains only luma samples.</param>
|
|||
/// <param name="mode">The resolved H.273 conversion mode.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
private static void ConvertYuvToRgbRow( |
|||
Span<float> red, |
|||
Span<float> green, |
|||
Span<float> blue, |
|||
bool isMonochrome, |
|||
ConversionMode mode, |
|||
in YuvToRgbParameters parameters) |
|||
{ |
|||
if (isMonochrome) |
|||
{ |
|||
ConvertYuvToRgbRow<MonochromeOperator>(red, green, blue, in parameters); |
|||
return; |
|||
} |
|||
|
|||
switch (mode) |
|||
{ |
|||
case ConversionMode.Identity: |
|||
ConvertYuvToRgbRow<IdentityOperator>(red, green, blue, in parameters); |
|||
break; |
|||
case ConversionMode.YCgCo: |
|||
ConvertYuvToRgbRow<YCgCoOperator>(red, green, blue, in parameters); |
|||
break; |
|||
case ConversionMode.Smpte2085: |
|||
ConvertYuvToRgbRow<Smpte2085Operator>(red, green, blue, in parameters); |
|||
break; |
|||
case ConversionMode.ConstantLuminance: |
|||
ConvertYuvToRgbRow<ConstantLuminanceOperator>(red, green, blue, in parameters); |
|||
break; |
|||
case ConversionMode.ICtCp when parameters.TransferCharacteristics == OpenBitstreamUnit.ObuTransferCharacteristics.Hlg: |
|||
ConvertYuvToRgbRow<ICtCpHlgOperator>(red, green, blue, in parameters); |
|||
break; |
|||
case ConversionMode.ICtCp: |
|||
ConvertYuvToRgbRow<ICtCpOperator>(red, green, blue, in parameters); |
|||
break; |
|||
default: |
|||
ConvertYuvToRgbRow<CoefficientsOperator>(red, green, blue, in parameters); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one component row using the selected static scalar and SIMD operator.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The matrix-specific conversion operator.</typeparam>
|
|||
/// <param name="red">The luma row, replaced by red.</param>
|
|||
/// <param name="green">The blue-difference row, replaced by green.</param>
|
|||
/// <param name="blue">The red-difference row, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
private static void ConvertYuvToRgbRow<TOperator>(Span<float> red, Span<float> green, Span<float> blue, in YuvToRgbParameters parameters) |
|||
where TOperator : struct, IYuvToRgbOperator |
|||
{ |
|||
ref float redBase = ref MemoryMarshal.GetReference(red); |
|||
ref float greenBase = ref MemoryMarshal.GetReference(green); |
|||
ref float blueBase = ref MemoryMarshal.GetReference(blue); |
|||
int length = red.Length; |
|||
int i = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
Vector512<float> lumaBias = Vector512.Create(parameters.LumaBias); |
|||
Vector512<float> inverseLumaScale = Vector512.Create(1F / parameters.LumaScale); |
|||
Vector512<float> chromaBias = Vector512.Create(parameters.ChromaBias); |
|||
Vector512<float> inverseChromaScale = Vector512.Create(1F / parameters.ChromaScale); |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
ref Vector512<float> redVector = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref redBase, i)); |
|||
Vector512<float> y = (redVector - lumaBias) * inverseLumaScale; |
|||
Vector512<float> cb = default; |
|||
Vector512<float> cr = default; |
|||
if (TOperator.UsesChroma) |
|||
{ |
|||
cb = (Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref greenBase, i)) - chromaBias) * inverseChromaScale; |
|||
cr = (Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref blueBase, i)) - chromaBias) * inverseChromaScale; |
|||
} |
|||
|
|||
TOperator.Convert(ref y, ref cb, ref cr, in parameters); |
|||
redVector = y; |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref greenBase, i)) = cb; |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref blueBase, i)) = cr; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
Vector256<float> lumaBias = Vector256.Create(parameters.LumaBias); |
|||
Vector256<float> inverseLumaScale = Vector256.Create(1F / parameters.LumaScale); |
|||
Vector256<float> chromaBias = Vector256.Create(parameters.ChromaBias); |
|||
Vector256<float> inverseChromaScale = Vector256.Create(1F / parameters.ChromaScale); |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
ref Vector256<float> redVector = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref redBase, i)); |
|||
Vector256<float> y = (redVector - lumaBias) * inverseLumaScale; |
|||
Vector256<float> cb = default; |
|||
Vector256<float> cr = default; |
|||
if (TOperator.UsesChroma) |
|||
{ |
|||
cb = (Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref greenBase, i)) - chromaBias) * inverseChromaScale; |
|||
cr = (Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref blueBase, i)) - chromaBias) * inverseChromaScale; |
|||
} |
|||
|
|||
TOperator.Convert(ref y, ref cb, ref cr, in parameters); |
|||
redVector = y; |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref greenBase, i)) = cb; |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref blueBase, i)) = cr; |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
Vector128<float> lumaBias = Vector128.Create(parameters.LumaBias); |
|||
Vector128<float> inverseLumaScale = Vector128.Create(1F / parameters.LumaScale); |
|||
Vector128<float> chromaBias = Vector128.Create(parameters.ChromaBias); |
|||
Vector128<float> inverseChromaScale = Vector128.Create(1F / parameters.ChromaScale); |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
ref Vector128<float> redVector = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref redBase, i)); |
|||
Vector128<float> y = (redVector - lumaBias) * inverseLumaScale; |
|||
Vector128<float> cb = default; |
|||
Vector128<float> cr = default; |
|||
if (TOperator.UsesChroma) |
|||
{ |
|||
cb = (Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref greenBase, i)) - chromaBias) * inverseChromaScale; |
|||
cr = (Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref blueBase, i)) - chromaBias) * inverseChromaScale; |
|||
} |
|||
|
|||
TOperator.Convert(ref y, ref cb, ref cr, in parameters); |
|||
redVector = y; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref greenBase, i)) = cb; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref blueBase, i)) = cr; |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
float y = (Unsafe.Add(ref redBase, i) - parameters.LumaBias) / parameters.LumaScale; |
|||
float cb = TOperator.UsesChroma ? (Unsafe.Add(ref greenBase, i) - parameters.ChromaBias) / parameters.ChromaScale : 0F; |
|||
float cr = TOperator.UsesChroma ? (Unsafe.Add(ref blueBase, i) - parameters.ChromaBias) / parameters.ChromaScale : 0F; |
|||
TOperator.Convert(ref y, ref cb, ref cr, in parameters); |
|||
Unsafe.Add(ref redBase, i) = y; |
|||
Unsafe.Add(ref greenBase, i) = cb; |
|||
Unsafe.Add(ref blueBase, i) = cr; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs normalized RGB component rows into opaque eight-bit RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red components.</param>
|
|||
/// <param name="green">The normalized green components.</param>
|
|||
/// <param name="blue">The normalized blue components.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
private static void PackRgba32(ReadOnlySpan<float> red, ReadOnlySpan<float> green, ReadOnlySpan<float> blue, Span<Rgba32> destination) |
|||
{ |
|||
ref float redBase = ref MemoryMarshal.GetReference(red); |
|||
ref float greenBase = ref MemoryMarshal.GetReference(green); |
|||
ref float blueBase = ref MemoryMarshal.GetReference(blue); |
|||
ref Rgba32 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int length = destination.Length; |
|||
int i = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref redBase, i)), ByteMaximum); |
|||
Vector512<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref greenBase, i)), ByteMaximum); |
|||
Vector512<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref blueBase, i)), ByteMaximum); |
|||
StoreRgba32Batch(r.GetLower().GetLower(), g.GetLower().GetLower(), b.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, i)); |
|||
StoreRgba32Batch(r.GetLower().GetUpper(), g.GetLower().GetUpper(), b.GetLower().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 4)); |
|||
StoreRgba32Batch(r.GetUpper().GetLower(), g.GetUpper().GetLower(), b.GetUpper().GetLower(), ref Unsafe.Add(ref destinationBase, i + 8)); |
|||
StoreRgba32Batch(r.GetUpper().GetUpper(), g.GetUpper().GetUpper(), b.GetUpper().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 12)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref redBase, i)), ByteMaximum); |
|||
Vector256<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref greenBase, i)), ByteMaximum); |
|||
Vector256<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref blueBase, i)), ByteMaximum); |
|||
StoreRgba32Batch(r.GetLower(), g.GetLower(), b.GetLower(), ref Unsafe.Add(ref destinationBase, i)); |
|||
StoreRgba32Batch(r.GetUpper(), g.GetUpper(), b.GetUpper(), ref Unsafe.Add(ref destinationBase, i + 4)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref redBase, i)), ByteMaximum); |
|||
Vector128<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref greenBase, i)), ByteMaximum); |
|||
Vector128<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref blueBase, i)), ByteMaximum); |
|||
StoreRgba32Batch(r, g, b, ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = new Rgba32( |
|||
ToSample<byte>(Unsafe.Add(ref redBase, i) * ByteMaximum, ByteMaximum), |
|||
ToSample<byte>(Unsafe.Add(ref greenBase, i) * ByteMaximum, ByteMaximum), |
|||
ToSample<byte>(Unsafe.Add(ref blueBase, i) * ByteMaximum, ByteMaximum)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Packs normalized RGB component rows into opaque 16-bit RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red components.</param>
|
|||
/// <param name="green">The normalized green components.</param>
|
|||
/// <param name="blue">The normalized blue components.</param>
|
|||
/// <param name="destination">The destination pixels.</param>
|
|||
private static void PackRgba64(ReadOnlySpan<float> red, ReadOnlySpan<float> green, ReadOnlySpan<float> blue, Span<Rgba64> destination) |
|||
{ |
|||
ref float redBase = ref MemoryMarshal.GetReference(red); |
|||
ref float greenBase = ref MemoryMarshal.GetReference(green); |
|||
ref float blueBase = ref MemoryMarshal.GetReference(blue); |
|||
ref Rgba64 destinationBase = ref MemoryMarshal.GetReference(destination); |
|||
int length = destination.Length; |
|||
int i = 0; |
|||
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref redBase, i)), UShortMaximum); |
|||
Vector512<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref greenBase, i)), UShortMaximum); |
|||
Vector512<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref blueBase, i)), UShortMaximum); |
|||
StoreRgba64Batch(r.GetLower().GetLower(), g.GetLower().GetLower(), b.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, i)); |
|||
StoreRgba64Batch(r.GetLower().GetUpper(), g.GetLower().GetUpper(), b.GetLower().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 4)); |
|||
StoreRgba64Batch(r.GetUpper().GetLower(), g.GetUpper().GetLower(), b.GetUpper().GetLower(), ref Unsafe.Add(ref destinationBase, i + 8)); |
|||
StoreRgba64Batch(r.GetUpper().GetUpper(), g.GetUpper().GetUpper(), b.GetUpper().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 12)); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref redBase, i)), UShortMaximum); |
|||
Vector256<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref greenBase, i)), UShortMaximum); |
|||
Vector256<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref blueBase, i)), UShortMaximum); |
|||
StoreRgba64Batch(r.GetLower(), g.GetLower(), b.GetLower(), ref Unsafe.Add(ref destinationBase, i)); |
|||
StoreRgba64Batch(r.GetUpper(), g.GetUpper(), b.GetUpper(), ref Unsafe.Add(ref destinationBase, i + 4)); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<int> r = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref redBase, i)), UShortMaximum); |
|||
Vector128<int> g = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref greenBase, i)), UShortMaximum); |
|||
Vector128<int> b = ScaleRoundAndClampToInt32(Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref blueBase, i)), UShortMaximum); |
|||
StoreRgba64Batch(r, g, b, ref Unsafe.Add(ref destinationBase, i)); |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationBase, i) = new Rgba64( |
|||
ToSample<ushort>(Unsafe.Add(ref redBase, i) * UShortMaximum, UShortMaximum), |
|||
ToSample<ushort>(Unsafe.Add(ref greenBase, i) * UShortMaximum, UShortMaximum), |
|||
ToSample<ushort>(Unsafe.Add(ref blueBase, i) * UShortMaximum, UShortMaximum), |
|||
ushort.MaxValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales, rounds, and clamps four normalized components to integer storage values.
|
|||
/// </summary>
|
|||
/// <param name="value">The normalized component values.</param>
|
|||
/// <param name="maximum">The largest storage value.</param>
|
|||
/// <returns>The bounded integer values.</returns>
|
|||
private static Vector128<int> ScaleRoundAndClampToInt32(Vector128<float> value, float maximum) |
|||
{ |
|||
Vector128<float> scaled = value * Vector128.Create(maximum); |
|||
Vector128<float> bounded = Vector128.Min(Vector128.Max(scaled, Vector128<float>.Zero), Vector128.Create(maximum)); |
|||
return Vector128.ConvertToInt32(Vector128.Round(bounded, MidpointRounding.AwayFromZero)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales, rounds, and clamps eight normalized components to integer storage values.
|
|||
/// </summary>
|
|||
/// <param name="value">The normalized component values.</param>
|
|||
/// <param name="maximum">The largest storage value.</param>
|
|||
/// <returns>The bounded integer values.</returns>
|
|||
private static Vector256<int> ScaleRoundAndClampToInt32(Vector256<float> value, float maximum) |
|||
{ |
|||
Vector256<float> scaled = value * Vector256.Create(maximum); |
|||
Vector256<float> bounded = Vector256.Min(Vector256.Max(scaled, Vector256<float>.Zero), Vector256.Create(maximum)); |
|||
return Vector256.ConvertToInt32(Vector256.Round(bounded, MidpointRounding.AwayFromZero)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Scales, rounds, and clamps sixteen normalized components to integer storage values.
|
|||
/// </summary>
|
|||
/// <param name="value">The normalized component values.</param>
|
|||
/// <param name="maximum">The largest storage value.</param>
|
|||
/// <returns>The bounded integer values.</returns>
|
|||
private static Vector512<int> ScaleRoundAndClampToInt32(Vector512<float> value, float maximum) |
|||
{ |
|||
Vector512<float> scaled = value * Vector512.Create(maximum); |
|||
Vector512<float> bounded = Vector512.Min(Vector512.Max(scaled, Vector512<float>.Zero), Vector512.Create(maximum)); |
|||
return Vector512.ConvertToInt32(Vector512.Round(bounded, MidpointRounding.AwayFromZero)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interleaves four red, green, and blue integer lanes into four opaque eight-bit RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="red">The red component values.</param>
|
|||
/// <param name="green">The green component values.</param>
|
|||
/// <param name="blue">The blue component values.</param>
|
|||
/// <param name="destination">The first destination pixel.</param>
|
|||
private static void StoreRgba32Batch(Vector128<int> red, Vector128<int> green, Vector128<int> blue, ref Rgba32 destination) |
|||
{ |
|||
Vector128<byte> red8 = Vector128.Narrow(Vector128.Narrow(red.AsUInt32(), Vector128<uint>.Zero), Vector128<ushort>.Zero); |
|||
Vector128<byte> green8 = Vector128.Narrow(Vector128.Narrow(green.AsUInt32(), Vector128<uint>.Zero), Vector128<ushort>.Zero); |
|||
Vector128<byte> blue8 = Vector128.Narrow(Vector128.Narrow(blue.AsUInt32(), Vector128<uint>.Zero), Vector128<ushort>.Zero); |
|||
Vector128<byte> alpha8 = Vector128.Create(byte.MaxValue); |
|||
Vector128<byte> redGreen = Vector128_.UnpackLow(red8, green8); |
|||
Vector128<byte> blueAlpha = Vector128_.UnpackLow(blue8, alpha8); |
|||
|
|||
// Interleaving the byte pairs as 16-bit lanes produces RGBA memory order on every supported
|
|||
// architecture without relying on the host integer endianness of Rgba32.PackedValue.
|
|||
Vector128<byte> rgba = Vector128_.UnpackLow(redGreen.AsInt16(), blueAlpha.AsInt16()).AsByte(); |
|||
Unsafe.As<Rgba32, Vector128<byte>>(ref destination) = rgba; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Interleaves four red, green, and blue integer lanes into four opaque 16-bit RGBA pixels.
|
|||
/// </summary>
|
|||
/// <param name="red">The red component values.</param>
|
|||
/// <param name="green">The green component values.</param>
|
|||
/// <param name="blue">The blue component values.</param>
|
|||
/// <param name="destination">The first destination pixel.</param>
|
|||
private static void StoreRgba64Batch(Vector128<int> red, Vector128<int> green, Vector128<int> blue, ref Rgba64 destination) |
|||
{ |
|||
Vector128<ushort> red16 = Vector128.Narrow(red.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<ushort> green16 = Vector128.Narrow(green.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<ushort> blue16 = Vector128.Narrow(blue.AsUInt32(), Vector128<uint>.Zero); |
|||
Vector128<ushort> alpha16 = Vector128.Create(ushort.MaxValue); |
|||
Vector128<ushort> redGreen = Vector128_.UnpackLow(red16.AsInt16(), green16.AsInt16()).AsUInt16(); |
|||
Vector128<ushort> blueAlpha = Vector128_.UnpackLow(blue16.AsInt16(), alpha16.AsInt16()).AsUInt16(); |
|||
Vector128<uint> lower = Vector128_.UnpackLow(redGreen.AsInt32(), blueAlpha.AsInt32()).AsUInt32(); |
|||
Vector128<uint> upper = Vector128_.UnpackHigh(redGreen.AsInt32(), blueAlpha.AsInt32()).AsUInt32(); |
|||
Unsafe.As<Rgba64, Vector128<uint>>(ref destination) = lower; |
|||
Unsafe.As<Rgba64, Vector128<uint>>(ref Unsafe.Add(ref destination, 2)) = upper; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens reconstructed eight-bit samples using exact unsigned conversions.
|
|||
/// </summary>
|
|||
private readonly struct ByteSampleLoader : ISampleLoader<byte> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LoadVector128(ref byte source) |
|||
{ |
|||
uint packed = Unsafe.ReadUnaligned<uint>(ref source); |
|||
Vector128<ushort> samples16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
return Vector128.ConvertToSingle(Vector128.WidenLower(samples16)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LoadVector256(ref byte source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref source); |
|||
Vector128<ushort> samples16 = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); |
|||
Vector256<uint> samples32 = Vector256.Create(Vector128.WidenLower(samples16), Vector128.WidenUpper(samples16)); |
|||
return Vector256.ConvertToSingle(samples32); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LoadVector512(ref byte source) |
|||
{ |
|||
Vector128<byte> packed = Unsafe.ReadUnaligned<Vector128<byte>>(ref source); |
|||
(Vector128<ushort> lower16, Vector128<ushort> upper16) = Vector128.Widen(packed); |
|||
Vector256<uint> lower32 = Vector256.Create(Vector128.WidenLower(lower16), Vector128.WidenUpper(lower16)); |
|||
Vector256<uint> upper32 = Vector256.Create(Vector128.WidenLower(upper16), Vector128.WidenUpper(upper16)); |
|||
return Vector512.ConvertToSingle(Vector512.Create(lower32, upper32)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Widens reconstructed high-bit-depth samples using exact unsigned conversions.
|
|||
/// </summary>
|
|||
private readonly struct UShortSampleLoader : ISampleLoader<ushort> |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static Vector128<float> LoadVector128(ref ushort source) |
|||
{ |
|||
ulong packed = Unsafe.ReadUnaligned<ulong>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector128<ushort> samples16 = Vector128.CreateScalarUnsafe(packed).AsUInt16(); |
|||
return Vector128.ConvertToSingle(Vector128.WidenLower(samples16)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector256<float> LoadVector256(ref ushort source) |
|||
{ |
|||
Vector128<ushort> samples16 = Unsafe.ReadUnaligned<Vector128<ushort>>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
Vector256<uint> samples32 = Vector256.Create(Vector128.WidenLower(samples16), Vector128.WidenUpper(samples16)); |
|||
return Vector256.ConvertToSingle(samples32); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public static Vector512<float> LoadVector512(ref ushort source) |
|||
{ |
|||
Vector256<ushort> samples16 = Unsafe.ReadUnaligned<Vector256<ushort>>(ref Unsafe.As<ushort, byte>(ref source)); |
|||
(Vector256<uint> lower32, Vector256<uint> upper32) = Vector256.Widen(samples16); |
|||
return Vector512.ConvertToSingle(Vector512.Create(lower32, upper32)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,157 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Verifies scalar and SIMD parity for every H.273 transfer characteristic consumed by AV1 color conversion.
|
|||
/// </summary>
|
|||
[Trait("Format", "Avif")] |
|||
public class Av1TransferFunctionsTests |
|||
{ |
|||
private static readonly float[] SignalValues = |
|||
[ |
|||
-0.5F, |
|||
-0.25F, |
|||
-0.081247F, |
|||
-0.01F, |
|||
0F, |
|||
0.0031308F, |
|||
0.01F, |
|||
0.04045F, |
|||
1F / 12F, |
|||
0.18F, |
|||
0.25F, |
|||
0.5F, |
|||
0.75F, |
|||
1F, |
|||
1.25F, |
|||
2F, |
|||
]; |
|||
|
|||
/// <summary>
|
|||
/// Gets every defined AV1-signallable transfer characteristic, including the deterministic unspecified fallback.
|
|||
/// </summary>
|
|||
public static TheoryData<int> TransferCharacteristics { get; } = new() |
|||
{ |
|||
(int)ObuTransferCharacteristics.Bt709, |
|||
(int)ObuTransferCharacteristics.Unspecified, |
|||
(int)ObuTransferCharacteristics.Bt470M, |
|||
(int)ObuTransferCharacteristics.Bt470BG, |
|||
(int)ObuTransferCharacteristics.Bt601, |
|||
(int)ObuTransferCharacteristics.Smpte240, |
|||
(int)ObuTransferCharacteristics.Linear, |
|||
(int)ObuTransferCharacteristics.Log100, |
|||
(int)ObuTransferCharacteristics.Log100Sqrt10, |
|||
(int)ObuTransferCharacteristics.Iec61966, |
|||
(int)ObuTransferCharacteristics.Bt1361, |
|||
(int)ObuTransferCharacteristics.Srgb, |
|||
(int)ObuTransferCharacteristics.Bt202010Bit, |
|||
(int)ObuTransferCharacteristics.Bt202012Bit, |
|||
(int)ObuTransferCharacteristics.Smpte2084, |
|||
(int)ObuTransferCharacteristics.Smpte428, |
|||
(int)ObuTransferCharacteristics.Hlg, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Verifies that every SIMD width matches the scalar inverse transfer function at curve transitions, extrema, and extended-range values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristicsValue">The transfer-characteristic code point under test.</param>
|
|||
[Theory] |
|||
[MemberData(nameof(TransferCharacteristics))] |
|||
public void ToLinearSimdMatchesScalar(int transferCharacteristicsValue) |
|||
{ |
|||
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue; |
|||
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToLinear(transferCharacteristics, value)).ToArray(); |
|||
|
|||
Vector128<float> vector128 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count))); |
|||
Vector256<float> vector256 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count))); |
|||
Vector512<float> vector512 = Av1TransferFunctions.ToLinear(transferCharacteristics, Vector512.Create(SignalValues)); |
|||
|
|||
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics); |
|||
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics); |
|||
AssertVectorMatchesScalar(expected, vector512, transferCharacteristics); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies that every SIMD width matches the scalar forward transfer function at curve transitions, extrema, and extended-range values.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristicsValue">The transfer-characteristic code point under test.</param>
|
|||
[Theory] |
|||
[MemberData(nameof(TransferCharacteristics))] |
|||
public void ToGammaSimdMatchesScalar(int transferCharacteristicsValue) |
|||
{ |
|||
ObuTransferCharacteristics transferCharacteristics = (ObuTransferCharacteristics)transferCharacteristicsValue; |
|||
float[] expected = SignalValues.Select(value => Av1TransferFunctions.ToGamma(transferCharacteristics, value)).ToArray(); |
|||
|
|||
Vector128<float> vector128 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector128.Create(SignalValues.AsSpan(0, Vector128<float>.Count))); |
|||
Vector256<float> vector256 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector256.Create(SignalValues.AsSpan(0, Vector256<float>.Count))); |
|||
Vector512<float> vector512 = Av1TransferFunctions.ToGamma(transferCharacteristics, Vector512.Create(SignalValues)); |
|||
|
|||
AssertVectorMatchesScalar(expected, vector128, transferCharacteristics); |
|||
AssertVectorMatchesScalar(expected, vector256, transferCharacteristics); |
|||
AssertVectorMatchesScalar(expected, vector512, transferCharacteristics); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares four SIMD lanes with their scalar results.
|
|||
/// </summary>
|
|||
/// <param name="expected">The scalar results.</param>
|
|||
/// <param name="actual">The SIMD results.</param>
|
|||
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
|
|||
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector128<float> actual, ObuTransferCharacteristics transferCharacteristics) |
|||
{ |
|||
for (int i = 0; i < Vector128<float>.Count; i++) |
|||
{ |
|||
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 128); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares eight SIMD lanes with their scalar results.
|
|||
/// </summary>
|
|||
/// <param name="expected">The scalar results.</param>
|
|||
/// <param name="actual">The SIMD results.</param>
|
|||
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
|
|||
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector256<float> actual, ObuTransferCharacteristics transferCharacteristics) |
|||
{ |
|||
for (int i = 0; i < Vector256<float>.Count; i++) |
|||
{ |
|||
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 256); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compares sixteen SIMD lanes with their scalar results.
|
|||
/// </summary>
|
|||
/// <param name="expected">The scalar results.</param>
|
|||
/// <param name="actual">The SIMD results.</param>
|
|||
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
|
|||
private static void AssertVectorMatchesScalar(ReadOnlySpan<float> expected, Vector512<float> actual, ObuTransferCharacteristics transferCharacteristics) |
|||
{ |
|||
for (int i = 0; i < Vector512<float>.Count; i++) |
|||
{ |
|||
AssertClose(expected[i], actual.GetElement(i), transferCharacteristics, i, 512); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Verifies one SIMD lane within the tolerance of the runtime vector exponential and logarithm kernels.
|
|||
/// </summary>
|
|||
/// <param name="expected">The scalar result.</param>
|
|||
/// <param name="actual">The SIMD result.</param>
|
|||
/// <param name="transferCharacteristics">The transfer characteristic under test.</param>
|
|||
/// <param name="lane">The SIMD lane index.</param>
|
|||
/// <param name="width">The SIMD register width.</param>
|
|||
private static void AssertClose(float expected, float actual, ObuTransferCharacteristics transferCharacteristics, int lane, int width) |
|||
{ |
|||
float tolerance = MathF.Max(2E-5F, MathF.Abs(expected) * 2E-5F); |
|||
Assert.True( |
|||
MathF.Abs(expected - actual) <= tolerance, |
|||
$"{transferCharacteristics} at {width}-bit lane {lane}: expected {expected:R}, actual {actual:R}, tolerance {tolerance:R}."); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:961bc38b61e60b7651fa20efa24269ae2f35e4958822a81c908c9bbf9b3f66e1 |
|||
size 21132 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:308bb51c3338ed9519cdaaa79186b934a21d1748d94120ed0348eb4f193d0988 |
|||
size 153217 |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:438b5d2bc7b32206b62676d6d460fb0037b857aa51dd03049867f68755227b3b |
|||
size 205370 |
|||
Loading…
Reference in new issue