mirror of https://github.com/SixLabors/ImageSharp
18 changed files with 3324 additions and 1680 deletions
@ -0,0 +1,155 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Stores the resolved H.273 values shared by every scalar and SIMD lane.
|
|||
/// </summary>
|
|||
internal readonly struct Av1ColorConversionParameters |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ColorConversionParameters"/> 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 Av1ColorConversionParameters( |
|||
float kr, |
|||
float kg, |
|||
float kb, |
|||
ObuTransferCharacteristics transferCharacteristics, |
|||
in Av1ConstantLuminanceScales constantLuminanceScales, |
|||
float lumaBias, |
|||
float lumaScale, |
|||
float chromaBias, |
|||
float chromaScale) |
|||
{ |
|||
this.Kr = kr; |
|||
this.Kg = kg; |
|||
this.Kb = kb; |
|||
this.RedChromaScale = 2F * (1F - kr); |
|||
this.BlueChromaScale = 2F * (1F - kb); |
|||
this.GreenRedChromaScale = 2F * kr * (1F - kr) / kg; |
|||
this.GreenBlueChromaScale = 2F * kb * (1F - kb) / kg; |
|||
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 red contribution from the red-difference component.
|
|||
/// </summary>
|
|||
public float RedChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue contribution from the blue-difference component.
|
|||
/// </summary>
|
|||
public float BlueChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the red-difference subtraction from green.
|
|||
/// </summary>
|
|||
public float GreenRedChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the blue-difference subtraction from green.
|
|||
/// </summary>
|
|||
public float GreenBlueChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the signaled transfer characteristics.
|
|||
/// </summary>
|
|||
public ObuTransferCharacteristics TransferCharacteristics { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the constant-luminance chroma scales.
|
|||
/// </summary>
|
|||
public Av1ConstantLuminanceScales 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>
|
|||
/// Stores the H.273 chroma normalization constants for constant-luminance conversion.
|
|||
/// </summary>
|
|||
internal readonly struct Av1ConstantLuminanceScales |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ConstantLuminanceScales"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="transferCharacteristics">The signaled transfer characteristics.</param>
|
|||
/// <param name="kr">The red luma coefficient.</param>
|
|||
/// <param name="kb">The blue luma coefficient.</param>
|
|||
public Av1ConstantLuminanceScales(ObuTransferCharacteristics transferCharacteristics, float kr, float kb) |
|||
{ |
|||
this.NegativeBlue = Av1TransferFunctions.ToGamma(transferCharacteristics, 1F - kb); |
|||
this.PositiveBlue = 1F - Av1TransferFunctions.ToGamma(transferCharacteristics, kb); |
|||
this.NegativeRed = Av1TransferFunctions.ToGamma(transferCharacteristics, 1F - kr); |
|||
this.PositiveRed = 1F - Av1TransferFunctions.ToGamma(transferCharacteristics, kr); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the negative blue-difference scale.
|
|||
/// </summary>
|
|||
public float NegativeBlue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the positive blue-difference scale.
|
|||
/// </summary>
|
|||
public float PositiveBlue { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the negative red-difference scale.
|
|||
/// </summary>
|
|||
public float NegativeRed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the positive red-difference scale.
|
|||
/// </summary>
|
|||
public float PositiveRed { get; } |
|||
} |
|||
@ -0,0 +1,156 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements coefficient-based YCbCr conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1CoefficientColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float y, ref float cb, ref float cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
// The resolved coefficients are frame invariants. Keeping the operator in this direct matrix form
|
|||
// lets every SIMD overload map the same three equations to fused multiply-add instructions.
|
|||
float r = y + (parameters.RedChromaScale * cr); |
|||
float g = y - (parameters.GreenRedChromaScale * cr) - (parameters.GreenBlueChromaScale * cb); |
|||
float b = y + (parameters.BlueChromaScale * cb); |
|||
|
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector128<float> r = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.RedChromaScale), cr, y); |
|||
Vector128<float> g = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(-parameters.GreenBlueChromaScale), |
|||
cb, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(-parameters.GreenRedChromaScale), cr, y)); |
|||
Vector128<float> b = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.BlueChromaScale), cb, y); |
|||
|
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector256<float> r = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.RedChromaScale), cr, y); |
|||
Vector256<float> g = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(-parameters.GreenBlueChromaScale), |
|||
cb, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(-parameters.GreenRedChromaScale), cr, y)); |
|||
Vector256<float> b = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.BlueChromaScale), cb, y); |
|||
|
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector512<float> r = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.RedChromaScale), cr, y); |
|||
Vector512<float> g = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(-parameters.GreenBlueChromaScale), |
|||
cb, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(-parameters.GreenRedChromaScale), cr, y)); |
|||
Vector512<float> b = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.BlueChromaScale), cb, y); |
|||
|
|||
y = r; |
|||
cb = g; |
|||
cr = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float r, |
|||
float g, |
|||
float b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float y, |
|||
out float cb, |
|||
out float cr) |
|||
{ |
|||
// Luma is shared by both chroma equations, so calculate it once before projecting blue and red.
|
|||
y = (parameters.Kr * r) + (parameters.Kg * g) + (parameters.Kb * b); |
|||
cb = (b - y) / parameters.BlueChromaScale; |
|||
cr = (r - y) / parameters.RedChromaScale; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> r, |
|||
Vector128<float> g, |
|||
Vector128<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> y, |
|||
out Vector128<float> cb, |
|||
out Vector128<float> cr) |
|||
{ |
|||
y = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(parameters.Kr), |
|||
r, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(parameters.Kg), g, Vector128.Create(parameters.Kb) * b)); |
|||
cb = (b - y) / Vector128.Create(parameters.BlueChromaScale); |
|||
cr = (r - y) / Vector128.Create(parameters.RedChromaScale); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> r, |
|||
Vector256<float> g, |
|||
Vector256<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> y, |
|||
out Vector256<float> cb, |
|||
out Vector256<float> cr) |
|||
{ |
|||
y = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(parameters.Kr), |
|||
r, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(parameters.Kg), g, Vector256.Create(parameters.Kb) * b)); |
|||
cb = (b - y) / Vector256.Create(parameters.BlueChromaScale); |
|||
cr = (r - y) / Vector256.Create(parameters.RedChromaScale); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> r, |
|||
Vector512<float> g, |
|||
Vector512<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> y, |
|||
out Vector512<float> cb, |
|||
out Vector512<float> cr) |
|||
{ |
|||
y = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(parameters.Kr), |
|||
r, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(parameters.Kg), g, Vector512.Create(parameters.Kb) * b)); |
|||
cb = (b - y) / Vector512.Create(parameters.BlueChromaScale); |
|||
cr = (r - y) / Vector512.Create(parameters.RedChromaScale); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,249 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements H.273 constant-luminance conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1ConstantLuminanceColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float y, ref float cb, ref float cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
|
|||
// Constant-luminance chroma has different positive and negative divisors. Reconstruct nonlinear
|
|||
// red and blue first, then solve for green in linear light using the signaled transfer curve.
|
|||
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/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cb, ref Vector128<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector128<float> blueScale = Vector128.ConditionalSelect( |
|||
Vector128.LessThanOrEqual(cb, Vector128<float>.Zero), |
|||
Vector128.Create(scales.NegativeBlue), |
|||
Vector128.Create(scales.PositiveBlue)); |
|||
Vector128<float> redScale = Vector128.ConditionalSelect( |
|||
Vector128.LessThanOrEqual(cr, Vector128<float>.Zero), |
|||
Vector128.Create(scales.NegativeRed), |
|||
Vector128.Create(scales.PositiveRed)); |
|||
Vector128<float> nonlinearBlue = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * blueScale, cb, y); |
|||
Vector128<float> nonlinearRed = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * redScale, cr, y); |
|||
Vector128<float> linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); |
|||
Vector128<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); |
|||
Vector128<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); |
|||
Vector128<float> linearGreen = ( |
|||
linearY - (Vector128.Create(parameters.Kr) * linearRed) - (Vector128.Create(parameters.Kb) * linearBlue)) |
|||
/ Vector128.Create(parameters.Kg); |
|||
|
|||
y = nonlinearRed; |
|||
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = nonlinearBlue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cb, ref Vector256<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector256<float> blueScale = Vector256.ConditionalSelect( |
|||
Vector256.LessThanOrEqual(cb, Vector256<float>.Zero), |
|||
Vector256.Create(scales.NegativeBlue), |
|||
Vector256.Create(scales.PositiveBlue)); |
|||
Vector256<float> redScale = Vector256.ConditionalSelect( |
|||
Vector256.LessThanOrEqual(cr, Vector256<float>.Zero), |
|||
Vector256.Create(scales.NegativeRed), |
|||
Vector256.Create(scales.PositiveRed)); |
|||
Vector256<float> nonlinearBlue = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * blueScale, cb, y); |
|||
Vector256<float> nonlinearRed = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * redScale, cr, y); |
|||
Vector256<float> linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); |
|||
Vector256<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); |
|||
Vector256<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); |
|||
Vector256<float> linearGreen = ( |
|||
linearY - (Vector256.Create(parameters.Kr) * linearRed) - (Vector256.Create(parameters.Kb) * linearBlue)) |
|||
/ Vector256.Create(parameters.Kg); |
|||
|
|||
y = nonlinearRed; |
|||
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = nonlinearBlue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cb, ref Vector512<float> cr, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector512<float> blueScale = Vector512.ConditionalSelect( |
|||
Vector512.LessThanOrEqual(cb, Vector512<float>.Zero), |
|||
Vector512.Create(scales.NegativeBlue), |
|||
Vector512.Create(scales.PositiveBlue)); |
|||
Vector512<float> redScale = Vector512.ConditionalSelect( |
|||
Vector512.LessThanOrEqual(cr, Vector512<float>.Zero), |
|||
Vector512.Create(scales.NegativeRed), |
|||
Vector512.Create(scales.PositiveRed)); |
|||
Vector512<float> nonlinearBlue = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * blueScale, cb, y); |
|||
Vector512<float> nonlinearRed = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * redScale, cr, y); |
|||
Vector512<float> linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y); |
|||
Vector512<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue); |
|||
Vector512<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed); |
|||
Vector512<float> linearGreen = ( |
|||
linearY - (Vector512.Create(parameters.Kr) * linearRed) - (Vector512.Create(parameters.Kb) * linearBlue)) |
|||
/ Vector512.Create(parameters.Kg); |
|||
|
|||
y = nonlinearRed; |
|||
cb = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cr = nonlinearBlue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float r, |
|||
float g, |
|||
float b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float y, |
|||
out float cb, |
|||
out float cr) |
|||
{ |
|||
// Luma is formed in linear light. The nonlinear red and blue differences then choose the
|
|||
// sign-dependent denominators that define constant-luminance Cb and Cr.
|
|||
float linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); |
|||
float linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); |
|||
float linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); |
|||
float linearY = (parameters.Kr * linearRed) + (parameters.Kg * linearGreen) + (parameters.Kb * linearBlue); |
|||
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); |
|||
float blueDifference = b - y; |
|||
float redDifference = r - y; |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
cb = blueDifference / (2F * (blueDifference <= 0F ? scales.NegativeBlue : scales.PositiveBlue)); |
|||
cr = redDifference / (2F * (redDifference <= 0F ? scales.NegativeRed : scales.PositiveRed)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> r, |
|||
Vector128<float> g, |
|||
Vector128<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> y, |
|||
out Vector128<float> cb, |
|||
out Vector128<float> cr) |
|||
{ |
|||
Vector128<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); |
|||
Vector128<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); |
|||
Vector128<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); |
|||
Vector128<float> linearY = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(parameters.Kr), |
|||
linearRed, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(parameters.Kg), linearGreen, Vector128.Create(parameters.Kb) * linearBlue)); |
|||
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); |
|||
Vector128<float> blueDifference = b - y; |
|||
Vector128<float> redDifference = r - y; |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector128<float> blueScale = Vector128.ConditionalSelect( |
|||
Vector128.LessThanOrEqual(blueDifference, Vector128<float>.Zero), |
|||
Vector128.Create(scales.NegativeBlue), |
|||
Vector128.Create(scales.PositiveBlue)); |
|||
Vector128<float> redScale = Vector128.ConditionalSelect( |
|||
Vector128.LessThanOrEqual(redDifference, Vector128<float>.Zero), |
|||
Vector128.Create(scales.NegativeRed), |
|||
Vector128.Create(scales.PositiveRed)); |
|||
|
|||
cb = blueDifference / (Vector128.Create(2F) * blueScale); |
|||
cr = redDifference / (Vector128.Create(2F) * redScale); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> r, |
|||
Vector256<float> g, |
|||
Vector256<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> y, |
|||
out Vector256<float> cb, |
|||
out Vector256<float> cr) |
|||
{ |
|||
Vector256<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); |
|||
Vector256<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); |
|||
Vector256<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); |
|||
Vector256<float> linearY = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(parameters.Kr), |
|||
linearRed, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(parameters.Kg), linearGreen, Vector256.Create(parameters.Kb) * linearBlue)); |
|||
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); |
|||
Vector256<float> blueDifference = b - y; |
|||
Vector256<float> redDifference = r - y; |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector256<float> blueScale = Vector256.ConditionalSelect( |
|||
Vector256.LessThanOrEqual(blueDifference, Vector256<float>.Zero), |
|||
Vector256.Create(scales.NegativeBlue), |
|||
Vector256.Create(scales.PositiveBlue)); |
|||
Vector256<float> redScale = Vector256.ConditionalSelect( |
|||
Vector256.LessThanOrEqual(redDifference, Vector256<float>.Zero), |
|||
Vector256.Create(scales.NegativeRed), |
|||
Vector256.Create(scales.PositiveRed)); |
|||
|
|||
cb = blueDifference / (Vector256.Create(2F) * blueScale); |
|||
cr = redDifference / (Vector256.Create(2F) * redScale); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> r, |
|||
Vector512<float> g, |
|||
Vector512<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> y, |
|||
out Vector512<float> cb, |
|||
out Vector512<float> cr) |
|||
{ |
|||
Vector512<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r); |
|||
Vector512<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g); |
|||
Vector512<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b); |
|||
Vector512<float> linearY = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(parameters.Kr), |
|||
linearRed, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(parameters.Kg), linearGreen, Vector512.Create(parameters.Kb) * linearBlue)); |
|||
y = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearY); |
|||
Vector512<float> blueDifference = b - y; |
|||
Vector512<float> redDifference = r - y; |
|||
Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales; |
|||
Vector512<float> blueScale = Vector512.ConditionalSelect( |
|||
Vector512.LessThanOrEqual(blueDifference, Vector512<float>.Zero), |
|||
Vector512.Create(scales.NegativeBlue), |
|||
Vector512.Create(scales.PositiveBlue)); |
|||
Vector512<float> redScale = Vector512.ConditionalSelect( |
|||
Vector512.LessThanOrEqual(redDifference, Vector512<float>.Zero), |
|||
Vector512.Create(scales.NegativeRed), |
|||
Vector512.Create(scales.PositiveRed)); |
|||
|
|||
cb = blueDifference / (Vector512.Create(2F) * blueScale); |
|||
cr = redDifference / (Vector512.Create(2F) * redScale); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,526 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements BT.2100 ICtCp conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1ICtCpColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <summary>
|
|||
/// The PQ Ct contribution to nonlinear L.
|
|||
/// </summary>
|
|||
public const float PqCtToL = 0.008609037037932756F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ Cp contribution to nonlinear L.
|
|||
/// </summary>
|
|||
public const float PqCpToL = 0.11102962500302596F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ Ct contribution to nonlinear S.
|
|||
/// </summary>
|
|||
public const float PqCtToS = 0.5600313357106791F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ Cp contribution to nonlinear S.
|
|||
/// </summary>
|
|||
public const float PqCpToS = -0.32062717498731885F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG Ct contribution to nonlinear L.
|
|||
/// </summary>
|
|||
public const float HlgCtToL = 0.015718580108730413F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG Cp contribution to nonlinear L.
|
|||
/// </summary>
|
|||
public const float HlgCpToL = 0.2095810681164055F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG Ct contribution to nonlinear S.
|
|||
/// </summary>
|
|||
public const float HlgCtToS = 1.0212710798422342F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG Cp contribution to nonlinear S.
|
|||
/// </summary>
|
|||
public const float HlgCpToS = -0.6052744909924315F; |
|||
|
|||
/// <summary>
|
|||
/// The linear L contribution to red.
|
|||
/// </summary>
|
|||
public const float LToRed = 3.4366066943330784F; |
|||
|
|||
/// <summary>
|
|||
/// The linear M contribution to red.
|
|||
/// </summary>
|
|||
public const float MToRed = -2.50645211865627F; |
|||
|
|||
/// <summary>
|
|||
/// The linear S contribution to red.
|
|||
/// </summary>
|
|||
public const float SToRed = 0.06984542432319148F; |
|||
|
|||
/// <summary>
|
|||
/// The linear L contribution to green.
|
|||
/// </summary>
|
|||
public const float LToGreen = -0.7913295555989287F; |
|||
|
|||
/// <summary>
|
|||
/// The linear M contribution to green.
|
|||
/// </summary>
|
|||
public const float MToGreen = 1.9836004517922907F; |
|||
|
|||
/// <summary>
|
|||
/// The linear S contribution to green.
|
|||
/// </summary>
|
|||
public const float SToGreen = -0.192270896193362F; |
|||
|
|||
/// <summary>
|
|||
/// The linear L contribution to blue.
|
|||
/// </summary>
|
|||
public const float LToBlue = -0.025949899690592672F; |
|||
|
|||
/// <summary>
|
|||
/// The linear M contribution to blue.
|
|||
/// </summary>
|
|||
public const float MToBlue = -0.09891371471172644F; |
|||
|
|||
/// <summary>
|
|||
/// The linear S contribution to blue.
|
|||
/// </summary>
|
|||
public const float SToBlue = 1.1248636144023192F; |
|||
|
|||
/// <summary>
|
|||
/// The linear red contribution to L.
|
|||
/// </summary>
|
|||
public const float RedToL = 1688F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear green contribution to L.
|
|||
/// </summary>
|
|||
public const float GreenToL = 2146F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear blue contribution to L.
|
|||
/// </summary>
|
|||
public const float BlueToL = 262F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear red contribution to M.
|
|||
/// </summary>
|
|||
public const float RedToM = 683F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear green contribution to M.
|
|||
/// </summary>
|
|||
public const float GreenToM = 2951F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear blue contribution to M.
|
|||
/// </summary>
|
|||
public const float BlueToM = 462F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear red contribution to S.
|
|||
/// </summary>
|
|||
public const float RedToS = 99F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear green contribution to S.
|
|||
/// </summary>
|
|||
public const float GreenToS = 309F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The linear blue contribution to S.
|
|||
/// </summary>
|
|||
public const float BlueToS = 3688F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear L contribution to Ct.
|
|||
/// </summary>
|
|||
public const float PqLToCt = 6610F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear M contribution to Ct.
|
|||
/// </summary>
|
|||
public const float PqMToCt = -13613F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear S contribution to Ct.
|
|||
/// </summary>
|
|||
public const float PqSToCt = 7003F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear L contribution to Cp.
|
|||
/// </summary>
|
|||
public const float PqLToCp = 17933F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear M contribution to Cp.
|
|||
/// </summary>
|
|||
public const float PqMToCp = -17390F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The PQ nonlinear S contribution to Cp.
|
|||
/// </summary>
|
|||
public const float PqSToCp = -543F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear L contribution to Ct.
|
|||
/// </summary>
|
|||
public const float HlgLToCt = 3625F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear M contribution to Ct.
|
|||
/// </summary>
|
|||
public const float HlgMToCt = -7465F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear S contribution to Ct.
|
|||
/// </summary>
|
|||
public const float HlgSToCt = 3840F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear L contribution to Cp.
|
|||
/// </summary>
|
|||
public const float HlgLToCp = 9500F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear M contribution to Cp.
|
|||
/// </summary>
|
|||
public const float HlgMToCp = -9212F / 4096F; |
|||
|
|||
/// <summary>
|
|||
/// The HLG nonlinear S contribution to Cp.
|
|||
/// </summary>
|
|||
public const float HlgSToCp = -288F / 4096F; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float intensity, ref float ct, ref float cp, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
float ctToL = isHlg ? HlgCtToL : PqCtToL; |
|||
float cpToL = isHlg ? HlgCpToL : PqCpToL; |
|||
|
|||
// The inverse ICtCp matrix first reconstructs nonlinear LMS. The transfer curve is then
|
|||
// removed before the fixed LMS-to-RGB matrix and reapplied to the three output primaries.
|
|||
float nonlinearL = intensity + (ctToL * ct) + (cpToL * cp); |
|||
float nonlinearM = intensity - (ctToL * ct) - (cpToL * cp); |
|||
float nonlinearS = intensity + ((isHlg ? HlgCtToS : PqCtToS) * ct) + ((isHlg ? HlgCpToS : PqCpToS) * cp); |
|||
float linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
float linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
float linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
float linearRed = (LToRed * linearL) + (MToRed * linearM) + (SToRed * linearS); |
|||
float linearGreen = (LToGreen * linearL) + (MToGreen * linearM) + (SToGreen * linearS); |
|||
float linearBlue = (LToBlue * linearL) + (MToBlue * linearM) + (SToBlue * linearS); |
|||
|
|||
intensity = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
ct = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cp = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector128<float> intensity, |
|||
ref Vector128<float> ct, |
|||
ref Vector128<float> cp, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
Vector128<float> ctContribution = Vector128.Create(isHlg ? HlgCtToL : PqCtToL) * ct; |
|||
Vector128<float> cpContribution = Vector128.Create(isHlg ? HlgCpToL : PqCpToL) * cp; |
|||
Vector128<float> nonlinearL = intensity + ctContribution + cpContribution; |
|||
Vector128<float> nonlinearM = intensity - ctContribution - cpContribution; |
|||
Vector128<float> nonlinearS = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(isHlg ? HlgCpToS : PqCpToS), |
|||
cp, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity)); |
|||
Vector128<float> linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
Vector128<float> linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
Vector128<float> linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
Vector128<float> linearRed = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(SToRed), |
|||
linearS, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(MToRed), linearM, Vector128.Create(LToRed) * linearL)); |
|||
Vector128<float> linearGreen = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(SToGreen), |
|||
linearS, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(MToGreen), linearM, Vector128.Create(LToGreen) * linearL)); |
|||
Vector128<float> linearBlue = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(SToBlue), |
|||
linearS, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(MToBlue), linearM, Vector128.Create(LToBlue) * linearL)); |
|||
|
|||
intensity = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
ct = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cp = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector256<float> intensity, |
|||
ref Vector256<float> ct, |
|||
ref Vector256<float> cp, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
Vector256<float> ctContribution = Vector256.Create(isHlg ? HlgCtToL : PqCtToL) * ct; |
|||
Vector256<float> cpContribution = Vector256.Create(isHlg ? HlgCpToL : PqCpToL) * cp; |
|||
Vector256<float> nonlinearL = intensity + ctContribution + cpContribution; |
|||
Vector256<float> nonlinearM = intensity - ctContribution - cpContribution; |
|||
Vector256<float> nonlinearS = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(isHlg ? HlgCpToS : PqCpToS), |
|||
cp, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity)); |
|||
Vector256<float> linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
Vector256<float> linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
Vector256<float> linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
Vector256<float> linearRed = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(SToRed), |
|||
linearS, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(MToRed), linearM, Vector256.Create(LToRed) * linearL)); |
|||
Vector256<float> linearGreen = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(SToGreen), |
|||
linearS, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(MToGreen), linearM, Vector256.Create(LToGreen) * linearL)); |
|||
Vector256<float> linearBlue = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(SToBlue), |
|||
linearS, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(MToBlue), linearM, Vector256.Create(LToBlue) * linearL)); |
|||
|
|||
intensity = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
ct = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cp = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector512<float> intensity, |
|||
ref Vector512<float> ct, |
|||
ref Vector512<float> cp, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
Vector512<float> ctContribution = Vector512.Create(isHlg ? HlgCtToL : PqCtToL) * ct; |
|||
Vector512<float> cpContribution = Vector512.Create(isHlg ? HlgCpToL : PqCpToL) * cp; |
|||
Vector512<float> nonlinearL = intensity + ctContribution + cpContribution; |
|||
Vector512<float> nonlinearM = intensity - ctContribution - cpContribution; |
|||
Vector512<float> nonlinearS = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(isHlg ? HlgCpToS : PqCpToS), |
|||
cp, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity)); |
|||
Vector512<float> linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL); |
|||
Vector512<float> linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM); |
|||
Vector512<float> linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS); |
|||
Vector512<float> linearRed = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(SToRed), |
|||
linearS, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(MToRed), linearM, Vector512.Create(LToRed) * linearL)); |
|||
Vector512<float> linearGreen = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(SToGreen), |
|||
linearS, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(MToGreen), linearM, Vector512.Create(LToGreen) * linearL)); |
|||
Vector512<float> linearBlue = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(SToBlue), |
|||
linearS, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(MToBlue), linearM, Vector512.Create(LToBlue) * linearL)); |
|||
|
|||
intensity = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearRed); |
|||
ct = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearGreen); |
|||
cp = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearBlue); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float red, |
|||
float green, |
|||
float blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float intensity, |
|||
out float ct, |
|||
out float cp) |
|||
{ |
|||
// ICtCp is defined in nonlinear LMS. Convert RGB to linear light, apply the LMS matrix, then
|
|||
// apply the signaled PQ or HLG transfer curve before deriving intensity and the chroma axes.
|
|||
float linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); |
|||
float linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); |
|||
float linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); |
|||
float nonlinearL = Av1TransferFunctions.ToGamma( |
|||
parameters.TransferCharacteristics, |
|||
(RedToL * linearRed) + (GreenToL * linearGreen) + (BlueToL * linearBlue)); |
|||
float nonlinearM = Av1TransferFunctions.ToGamma( |
|||
parameters.TransferCharacteristics, |
|||
(RedToM * linearRed) + (GreenToM * linearGreen) + (BlueToM * linearBlue)); |
|||
float nonlinearS = Av1TransferFunctions.ToGamma( |
|||
parameters.TransferCharacteristics, |
|||
(RedToS * linearRed) + (GreenToS * linearGreen) + (BlueToS * linearBlue)); |
|||
intensity = 0.5F * (nonlinearL + nonlinearM); |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
ct = isHlg |
|||
? (HlgLToCt * nonlinearL) + (HlgMToCt * nonlinearM) + (HlgSToCt * nonlinearS) |
|||
: (PqLToCt * nonlinearL) + (PqMToCt * nonlinearM) + (PqSToCt * nonlinearS); |
|||
|
|||
cp = isHlg |
|||
? (HlgLToCp * nonlinearL) + (HlgMToCp * nonlinearM) + (HlgSToCp * nonlinearS) |
|||
: (PqLToCp * nonlinearL) + (PqMToCp * nonlinearM) + (PqSToCp * nonlinearS); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> red, |
|||
Vector128<float> green, |
|||
Vector128<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> intensity, |
|||
out Vector128<float> ct, |
|||
out Vector128<float> cp) |
|||
{ |
|||
Vector128<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); |
|||
Vector128<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); |
|||
Vector128<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); |
|||
Vector128<float> linearL = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(RedToL), |
|||
linearRed, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(GreenToL), linearGreen, Vector128.Create(BlueToL) * linearBlue)); |
|||
Vector128<float> linearM = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(RedToM), |
|||
linearRed, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(GreenToM), linearGreen, Vector128.Create(BlueToM) * linearBlue)); |
|||
Vector128<float> linearS = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(RedToS), |
|||
linearRed, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(GreenToS), linearGreen, Vector128.Create(BlueToS) * linearBlue)); |
|||
Vector128<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); |
|||
Vector128<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); |
|||
Vector128<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); |
|||
intensity = Vector128.Create(0.5F) * (nonlinearL + nonlinearM); |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
float lToCt = isHlg ? HlgLToCt : PqLToCt; |
|||
float mToCt = isHlg ? HlgMToCt : PqMToCt; |
|||
float sToCt = isHlg ? HlgSToCt : PqSToCt; |
|||
float lToCp = isHlg ? HlgLToCp : PqLToCp; |
|||
float mToCp = isHlg ? HlgMToCp : PqMToCp; |
|||
float sToCp = isHlg ? HlgSToCp : PqSToCp; |
|||
ct = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(lToCt), |
|||
nonlinearL, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(mToCt), nonlinearM, Vector128.Create(sToCt) * nonlinearS)); |
|||
cp = Vector128.MultiplyAddEstimate( |
|||
Vector128.Create(lToCp), |
|||
nonlinearL, |
|||
Vector128.MultiplyAddEstimate(Vector128.Create(mToCp), nonlinearM, Vector128.Create(sToCp) * nonlinearS)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> red, |
|||
Vector256<float> green, |
|||
Vector256<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> intensity, |
|||
out Vector256<float> ct, |
|||
out Vector256<float> cp) |
|||
{ |
|||
Vector256<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); |
|||
Vector256<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); |
|||
Vector256<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); |
|||
Vector256<float> linearL = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(RedToL), |
|||
linearRed, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(GreenToL), linearGreen, Vector256.Create(BlueToL) * linearBlue)); |
|||
Vector256<float> linearM = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(RedToM), |
|||
linearRed, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(GreenToM), linearGreen, Vector256.Create(BlueToM) * linearBlue)); |
|||
Vector256<float> linearS = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(RedToS), |
|||
linearRed, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(GreenToS), linearGreen, Vector256.Create(BlueToS) * linearBlue)); |
|||
Vector256<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); |
|||
Vector256<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); |
|||
Vector256<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); |
|||
intensity = Vector256.Create(0.5F) * (nonlinearL + nonlinearM); |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
float lToCt = isHlg ? HlgLToCt : PqLToCt; |
|||
float mToCt = isHlg ? HlgMToCt : PqMToCt; |
|||
float sToCt = isHlg ? HlgSToCt : PqSToCt; |
|||
float lToCp = isHlg ? HlgLToCp : PqLToCp; |
|||
float mToCp = isHlg ? HlgMToCp : PqMToCp; |
|||
float sToCp = isHlg ? HlgSToCp : PqSToCp; |
|||
ct = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(lToCt), |
|||
nonlinearL, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(mToCt), nonlinearM, Vector256.Create(sToCt) * nonlinearS)); |
|||
cp = Vector256.MultiplyAddEstimate( |
|||
Vector256.Create(lToCp), |
|||
nonlinearL, |
|||
Vector256.MultiplyAddEstimate(Vector256.Create(mToCp), nonlinearM, Vector256.Create(sToCp) * nonlinearS)); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> red, |
|||
Vector512<float> green, |
|||
Vector512<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> intensity, |
|||
out Vector512<float> ct, |
|||
out Vector512<float> cp) |
|||
{ |
|||
Vector512<float> linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red); |
|||
Vector512<float> linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green); |
|||
Vector512<float> linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue); |
|||
Vector512<float> linearL = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(RedToL), |
|||
linearRed, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(GreenToL), linearGreen, Vector512.Create(BlueToL) * linearBlue)); |
|||
Vector512<float> linearM = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(RedToM), |
|||
linearRed, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(GreenToM), linearGreen, Vector512.Create(BlueToM) * linearBlue)); |
|||
Vector512<float> linearS = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(RedToS), |
|||
linearRed, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(GreenToS), linearGreen, Vector512.Create(BlueToS) * linearBlue)); |
|||
Vector512<float> nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL); |
|||
Vector512<float> nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM); |
|||
Vector512<float> nonlinearS = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearS); |
|||
intensity = Vector512.Create(0.5F) * (nonlinearL + nonlinearM); |
|||
bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg; |
|||
float lToCt = isHlg ? HlgLToCt : PqLToCt; |
|||
float mToCt = isHlg ? HlgMToCt : PqMToCt; |
|||
float sToCt = isHlg ? HlgSToCt : PqSToCt; |
|||
float lToCp = isHlg ? HlgLToCp : PqLToCp; |
|||
float mToCp = isHlg ? HlgMToCp : PqMToCp; |
|||
float sToCp = isHlg ? HlgSToCp : PqSToCp; |
|||
ct = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(lToCt), |
|||
nonlinearL, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(mToCt), nonlinearM, Vector512.Create(sToCt) * nonlinearS)); |
|||
cp = Vector512.MultiplyAddEstimate( |
|||
Vector512.Create(lToCp), |
|||
nonlinearL, |
|||
Vector512.MultiplyAddEstimate(Vector512.Create(mToCp), nonlinearM, Vector512.Create(sToCp) * nonlinearS)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements direct G, B, and R plane mapping for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1IdentityColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => true; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float green, ref float blue, ref float red, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
// Identity-matrix AV1 stores the planes in G, B, R order. Rotate the three references in place
|
|||
// so the shared traversal always leaves component0/component1/component2 as R, G, B.
|
|||
float g = green; |
|||
green = red; |
|||
red = blue; |
|||
blue = g; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector128<float> green, |
|||
ref Vector128<float> blue, |
|||
ref Vector128<float> red, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector128<float> g = green; |
|||
green = red; |
|||
red = blue; |
|||
blue = g; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector256<float> green, |
|||
ref Vector256<float> blue, |
|||
ref Vector256<float> red, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector256<float> g = green; |
|||
green = red; |
|||
red = blue; |
|||
blue = g; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb( |
|||
ref Vector512<float> green, |
|||
ref Vector512<float> blue, |
|||
ref Vector512<float> red, |
|||
in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector512<float> g = green; |
|||
green = red; |
|||
red = blue; |
|||
blue = g; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float red, |
|||
float green, |
|||
float blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float component0, |
|||
out float component1, |
|||
out float component2) |
|||
{ |
|||
// Identity-matrix AV1 stores RGB input as G, B, R without matrix arithmetic.
|
|||
component0 = green; |
|||
component1 = blue; |
|||
component2 = red; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> red, |
|||
Vector128<float> green, |
|||
Vector128<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> component0, |
|||
out Vector128<float> component1, |
|||
out Vector128<float> component2) |
|||
{ |
|||
component0 = green; |
|||
component1 = blue; |
|||
component2 = red; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> red, |
|||
Vector256<float> green, |
|||
Vector256<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> component0, |
|||
out Vector256<float> component1, |
|||
out Vector256<float> component2) |
|||
{ |
|||
component0 = green; |
|||
component1 = blue; |
|||
component2 = red; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> red, |
|||
Vector512<float> green, |
|||
Vector512<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> component0, |
|||
out Vector512<float> component1, |
|||
out Vector512<float> component2) |
|||
{ |
|||
component0 = green; |
|||
component1 = blue; |
|||
component2 = red; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,427 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <content>
|
|||
/// Provides the static operator contract and SIMD traversal used by AV1 color converters.
|
|||
/// </content>
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Defines color-model arithmetic for scalar and SIMD lanes in both conversion directions.
|
|||
/// </summary>
|
|||
internal interface IAv1ColorOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether chroma uses the luma range rather than the centered chroma range.
|
|||
/// </summary>
|
|||
public static abstract bool ChromaUsesLumaRange { get; } |
|||
|
|||
/// <summary>
|
|||
/// Converts one normalized AV1 sample to RGB.
|
|||
/// </summary>
|
|||
/// <param name="component0">The first encoded component, replaced by red.</param>
|
|||
/// <param name="component1">The second encoded component, replaced by green.</param>
|
|||
/// <param name="component2">The third encoded component, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void ConvertToRgb( |
|||
ref float component0, |
|||
ref float component1, |
|||
ref float component2, |
|||
in Av1ColorConversionParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts four normalized AV1 samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="component0">The first encoded component lanes, replaced by red.</param>
|
|||
/// <param name="component1">The second encoded component lanes, replaced by green.</param>
|
|||
/// <param name="component2">The third encoded component lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void ConvertToRgb( |
|||
ref Vector128<float> component0, |
|||
ref Vector128<float> component1, |
|||
ref Vector128<float> component2, |
|||
in Av1ColorConversionParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight normalized AV1 samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="component0">The first encoded component lanes, replaced by red.</param>
|
|||
/// <param name="component1">The second encoded component lanes, replaced by green.</param>
|
|||
/// <param name="component2">The third encoded component lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void ConvertToRgb( |
|||
ref Vector256<float> component0, |
|||
ref Vector256<float> component1, |
|||
ref Vector256<float> component2, |
|||
in Av1ColorConversionParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts sixteen normalized AV1 samples to RGB.
|
|||
/// </summary>
|
|||
/// <param name="component0">The first encoded component lanes, replaced by red.</param>
|
|||
/// <param name="component1">The second encoded component lanes, replaced by green.</param>
|
|||
/// <param name="component2">The third encoded component lanes, replaced by blue.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
public static abstract void ConvertToRgb( |
|||
ref Vector512<float> component0, |
|||
ref Vector512<float> component1, |
|||
ref Vector512<float> component2, |
|||
in Av1ColorConversionParameters parameters); |
|||
|
|||
/// <summary>
|
|||
/// Converts one normalized RGB sample to AV1 components.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red component.</param>
|
|||
/// <param name="green">The normalized green component.</param>
|
|||
/// <param name="blue">The normalized blue component.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="component0">The first converted component.</param>
|
|||
/// <param name="component1">The second converted component.</param>
|
|||
/// <param name="component2">The third converted component.</param>
|
|||
public static abstract void ConvertFromRgb( |
|||
float red, |
|||
float green, |
|||
float blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float component0, |
|||
out float component1, |
|||
out float component2); |
|||
|
|||
/// <summary>
|
|||
/// Converts four normalized RGB samples to AV1 components.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red lanes.</param>
|
|||
/// <param name="green">The normalized green lanes.</param>
|
|||
/// <param name="blue">The normalized blue lanes.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="component0">The first converted component lanes.</param>
|
|||
/// <param name="component1">The second converted component lanes.</param>
|
|||
/// <param name="component2">The third converted component lanes.</param>
|
|||
public static abstract void ConvertFromRgb( |
|||
Vector128<float> red, |
|||
Vector128<float> green, |
|||
Vector128<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> component0, |
|||
out Vector128<float> component1, |
|||
out Vector128<float> component2); |
|||
|
|||
/// <summary>
|
|||
/// Converts eight normalized RGB samples to AV1 components.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red lanes.</param>
|
|||
/// <param name="green">The normalized green lanes.</param>
|
|||
/// <param name="blue">The normalized blue lanes.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="component0">The first converted component lanes.</param>
|
|||
/// <param name="component1">The second converted component lanes.</param>
|
|||
/// <param name="component2">The third converted component lanes.</param>
|
|||
public static abstract void ConvertFromRgb( |
|||
Vector256<float> red, |
|||
Vector256<float> green, |
|||
Vector256<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> component0, |
|||
out Vector256<float> component1, |
|||
out Vector256<float> component2); |
|||
|
|||
/// <summary>
|
|||
/// Converts sixteen normalized RGB samples to AV1 components.
|
|||
/// </summary>
|
|||
/// <param name="red">The normalized red lanes.</param>
|
|||
/// <param name="green">The normalized green lanes.</param>
|
|||
/// <param name="blue">The normalized blue lanes.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="component0">The first converted component lanes.</param>
|
|||
/// <param name="component1">The second converted component lanes.</param>
|
|||
/// <param name="component2">The third converted component lanes.</param>
|
|||
public static abstract void ConvertFromRgb( |
|||
Vector512<float> red, |
|||
Vector512<float> green, |
|||
Vector512<float> blue, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> component0, |
|||
out Vector512<float> component1, |
|||
out Vector512<float> component2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts an AV1 color model using one operator-driven traversal for all SIMD widths.
|
|||
/// </summary>
|
|||
/// <typeparam name="TOperator">The color-model-specific arithmetic.</typeparam>
|
|||
internal sealed class Av1ColorConverter<TOperator> : Av1ColorConverterBase |
|||
where TOperator : struct, IAv1ColorOperator |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ColorConverter{TOperator}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="isMonochrome">Whether the frame contains only luma samples.</param>
|
|||
public Av1ColorConverter(in Av1ColorConversionParameters parameters, bool isMonochrome) |
|||
: base(in parameters, isMonochrome) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override float ChromaScale => TOperator.ChromaUsesLumaRange ? this.Parameters.LumaScale : this.Parameters.ChromaScale; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override float ChromaBias => TOperator.ChromaUsesLumaRange ? this.Parameters.LumaBias : this.Parameters.ChromaBias; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ConvertToRgbInPlace(Span<float> component0, Span<float> component1, Span<float> component2) |
|||
{ |
|||
Av1ColorConversionParameters parameters = this.Parameters; |
|||
|
|||
// Row reconstruction owns equally sized planar buffers. As in JPEG, first-element byrefs let each
|
|||
// SIMD width share one offset while the closed operator type keeps color-model dispatch out of the loop.
|
|||
ref float component0Base = ref MemoryMarshal.GetReference(component0); |
|||
ref float component1Base = ref MemoryMarshal.GetReference(component1); |
|||
ref float component2Base = ref MemoryMarshal.GetReference(component2); |
|||
int length = component0.Length; |
|||
int i = 0; |
|||
|
|||
if (this.IsMonochrome) |
|||
{ |
|||
// Monochrome has no operator arithmetic: expanding the luma range once and copying each SIMD
|
|||
// vector to all three planes is cheaper than routing it through a three-component operator.
|
|||
if (Vector512.IsHardwareAccelerated && i <= length - Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> bias = Vector512.Create(parameters.LumaBias); |
|||
Vector512<float> inverseScale = Vector512.Create(1F / parameters.LumaScale); |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> value = (Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component0Base, i)) - bias) * inverseScale; |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component0Base, i)) = value; |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component1Base, i)) = value; |
|||
Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component2Base, i)) = value; |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && i <= length - Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> bias = Vector256.Create(parameters.LumaBias); |
|||
Vector256<float> inverseScale = Vector256.Create(1F / parameters.LumaScale); |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> value = (Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component0Base, i)) - bias) * inverseScale; |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component0Base, i)) = value; |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component1Base, i)) = value; |
|||
Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component2Base, i)) = value; |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated && i <= length - Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> bias = Vector128.Create(parameters.LumaBias); |
|||
Vector128<float> inverseScale = Vector128.Create(1F / parameters.LumaScale); |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> value = (Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component0Base, i)) - bias) * inverseScale; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component0Base, i)) = value; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component1Base, i)) = value; |
|||
Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component2Base, i)) = value; |
|||
} |
|||
} |
|||
|
|||
for (; i < length; i++) |
|||
{ |
|||
float value = (Unsafe.Add(ref component0Base, i) - parameters.LumaBias) / parameters.LumaScale; |
|||
Unsafe.Add(ref component0Base, i) = value; |
|||
Unsafe.Add(ref component1Base, i) = value; |
|||
Unsafe.Add(ref component2Base, i) = value; |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
float chromaBias = this.ChromaBias; |
|||
float chromaScale = this.ChromaScale; |
|||
|
|||
// Descending widths preserve vector execution for the remainder left by a wider register. The range
|
|||
// expansion is folded into each load so operators receive normalized H.273 components directly.
|
|||
if (Vector512.IsHardwareAccelerated && i <= length - Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> lumaBias = Vector512.Create(parameters.LumaBias); |
|||
Vector512<float> inverseLumaScale = Vector512.Create(1F / parameters.LumaScale); |
|||
Vector512<float> chromaBiasVector = Vector512.Create(chromaBias); |
|||
Vector512<float> inverseChromaScale = Vector512.Create(1F / chromaScale); |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
ref Vector512<float> c0 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector512<float> c1 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector512<float> c2 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
c0 = (c0 - lumaBias) * inverseLumaScale; |
|||
c1 = (c1 - chromaBiasVector) * inverseChromaScale; |
|||
c2 = (c2 - chromaBiasVector) * inverseChromaScale; |
|||
|
|||
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated && i <= length - Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> lumaBias = Vector256.Create(parameters.LumaBias); |
|||
Vector256<float> inverseLumaScale = Vector256.Create(1F / parameters.LumaScale); |
|||
Vector256<float> chromaBiasVector = Vector256.Create(chromaBias); |
|||
Vector256<float> inverseChromaScale = Vector256.Create(1F / chromaScale); |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
ref Vector256<float> c0 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector256<float> c1 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector256<float> c2 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
c0 = (c0 - lumaBias) * inverseLumaScale; |
|||
c1 = (c1 - chromaBiasVector) * inverseChromaScale; |
|||
c2 = (c2 - chromaBiasVector) * inverseChromaScale; |
|||
|
|||
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated && i <= length - Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> lumaBias = Vector128.Create(parameters.LumaBias); |
|||
Vector128<float> inverseLumaScale = Vector128.Create(1F / parameters.LumaScale); |
|||
Vector128<float> chromaBiasVector = Vector128.Create(chromaBias); |
|||
Vector128<float> inverseChromaScale = Vector128.Create(1F / chromaScale); |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
ref Vector128<float> c0 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector128<float> c1 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector128<float> c2 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
c0 = (c0 - lumaBias) * inverseLumaScale; |
|||
c1 = (c1 - chromaBiasVector) * inverseChromaScale; |
|||
c2 = (c2 - chromaBiasVector) * inverseChromaScale; |
|||
|
|||
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); |
|||
} |
|||
} |
|||
|
|||
// Scalar conversion is reserved for the zero-to-three samples left after the SIMD cascade.
|
|||
for (; i < length; i++) |
|||
{ |
|||
float c0 = (Unsafe.Add(ref component0Base, i) - parameters.LumaBias) / parameters.LumaScale; |
|||
float c1 = (Unsafe.Add(ref component1Base, i) - chromaBias) / chromaScale; |
|||
float c2 = (Unsafe.Add(ref component2Base, i) - chromaBias) / chromaScale; |
|||
TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); |
|||
Unsafe.Add(ref component0Base, i) = c0; |
|||
Unsafe.Add(ref component1Base, i) = c1; |
|||
Unsafe.Add(ref component2Base, i) = c2; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override void ConvertFromRgbInPlace( |
|||
Span<float> component0, |
|||
Span<float> component1, |
|||
Span<float> component2, |
|||
float maximumValue) |
|||
{ |
|||
Av1ColorConversionParameters parameters = this.Parameters; |
|||
|
|||
// The unpacker supplies three planar RGB rows. These same buffers become the destination component
|
|||
// rows after each operator call, so encoding retains JPEG's planar contract without another allocation.
|
|||
ref float component0Base = ref MemoryMarshal.GetReference(component0); |
|||
ref float component1Base = ref MemoryMarshal.GetReference(component1); |
|||
ref float component2Base = ref MemoryMarshal.GetReference(component2); |
|||
int length = component0.Length; |
|||
int i = 0; |
|||
|
|||
// RGB normalization is part of the vector load, and each operator returns planar components through
|
|||
// out parameters. This is the same input/output shape used by JPEG's encoder-side color operators.
|
|||
if (Vector512.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector512<float>.Count; |
|||
|
|||
if (i <= oneVectorFromEnd) |
|||
{ |
|||
Vector512<float> inverseMaximum = Vector512.Create(1F / maximumValue); |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector512<float>.Count) |
|||
{ |
|||
Vector512<float> red = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component0Base, i)) * inverseMaximum; |
|||
Vector512<float> green = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component1Base, i)) * inverseMaximum; |
|||
Vector512<float> blue = Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component2Base, i)) * inverseMaximum; |
|||
ref Vector512<float> c0 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector512<float> c1 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector512<float> c2 = ref Unsafe.As<float, Vector512<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
|
|||
TOperator.ConvertFromRgb(red, green, blue, in parameters, out c0, out c1, out c2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector256.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector256<float>.Count; |
|||
|
|||
if (i <= oneVectorFromEnd) |
|||
{ |
|||
Vector256<float> inverseMaximum = Vector256.Create(1F / maximumValue); |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector256<float>.Count) |
|||
{ |
|||
Vector256<float> red = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component0Base, i)) * inverseMaximum; |
|||
Vector256<float> green = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component1Base, i)) * inverseMaximum; |
|||
Vector256<float> blue = Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component2Base, i)) * inverseMaximum; |
|||
ref Vector256<float> c0 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector256<float> c1 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector256<float> c2 = ref Unsafe.As<float, Vector256<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
|
|||
TOperator.ConvertFromRgb(red, green, blue, in parameters, out c0, out c1, out c2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (Vector128.IsHardwareAccelerated) |
|||
{ |
|||
int oneVectorFromEnd = length - Vector128<float>.Count; |
|||
|
|||
if (i <= oneVectorFromEnd) |
|||
{ |
|||
Vector128<float> inverseMaximum = Vector128.Create(1F / maximumValue); |
|||
|
|||
for (; i <= oneVectorFromEnd; i += Vector128<float>.Count) |
|||
{ |
|||
Vector128<float> red = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component0Base, i)) * inverseMaximum; |
|||
Vector128<float> green = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component1Base, i)) * inverseMaximum; |
|||
Vector128<float> blue = Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component2Base, i)) * inverseMaximum; |
|||
ref Vector128<float> c0 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component0Base, i)); |
|||
ref Vector128<float> c1 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component1Base, i)); |
|||
ref Vector128<float> c2 = ref Unsafe.As<float, Vector128<float>>(ref Unsafe.Add(ref component2Base, i)); |
|||
|
|||
TOperator.ConvertFromRgb(red, green, blue, in parameters, out c0, out c1, out c2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
float inverseMaximumScalar = 1F / maximumValue; |
|||
|
|||
// The shared offset leaves at most three samples for the scalar fallback on SIMD-capable systems.
|
|||
for (; i < length; i++) |
|||
{ |
|||
float red = Unsafe.Add(ref component0Base, i) * inverseMaximumScalar; |
|||
float green = Unsafe.Add(ref component1Base, i) * inverseMaximumScalar; |
|||
float blue = Unsafe.Add(ref component2Base, i) * inverseMaximumScalar; |
|||
|
|||
TOperator.ConvertFromRgb(red, green, blue, in parameters, out float c0, out float c1, out float c2); |
|||
|
|||
Unsafe.Add(ref component0Base, i) = c0; |
|||
Unsafe.Add(ref component1Base, i) = c1; |
|||
Unsafe.Add(ref component2Base, i) = c2; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,143 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements SMPTE ST 2085 YDzDx conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1Smpte2085ColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <summary>
|
|||
/// The SMPTE ST 2085 blue primary normalization factor.
|
|||
/// </summary>
|
|||
public const float BlueNormalization = 0.986566F; |
|||
|
|||
/// <summary>
|
|||
/// The SMPTE ST 2085 green contribution to the red primary.
|
|||
/// </summary>
|
|||
public const float RedGreenContribution = 0.991902F; |
|||
|
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float y, ref float dz, ref float dx, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
// The encoded YDzDx planes carry green directly. The two difference planes restore blue and red.
|
|||
float g = y; |
|||
float b = ((2F * dz) + y) / BlueNormalization; |
|||
float r = (2F * dx) + (RedGreenContribution * y); |
|||
y = r; |
|||
dz = g; |
|||
dx = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> dz, ref Vector128<float> dx, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector128<float> g = y; |
|||
Vector128<float> b = Vector128.MultiplyAddEstimate(Vector128.Create(2F), dz, y) / Vector128.Create(BlueNormalization); |
|||
Vector128<float> r = Vector128.MultiplyAddEstimate(Vector128.Create(2F), dx, Vector128.Create(RedGreenContribution) * y); |
|||
y = r; |
|||
dz = g; |
|||
dx = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> dz, ref Vector256<float> dx, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector256<float> g = y; |
|||
Vector256<float> b = Vector256.MultiplyAddEstimate(Vector256.Create(2F), dz, y) / Vector256.Create(BlueNormalization); |
|||
Vector256<float> r = Vector256.MultiplyAddEstimate(Vector256.Create(2F), dx, Vector256.Create(RedGreenContribution) * y); |
|||
y = r; |
|||
dz = g; |
|||
dx = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> dz, ref Vector512<float> dx, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector512<float> g = y; |
|||
Vector512<float> b = Vector512.MultiplyAddEstimate(Vector512.Create(2F), dz, y) / Vector512.Create(BlueNormalization); |
|||
Vector512<float> r = Vector512.MultiplyAddEstimate(Vector512.Create(2F), dx, Vector512.Create(RedGreenContribution) * y); |
|||
y = r; |
|||
dz = g; |
|||
dx = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float r, |
|||
float g, |
|||
float b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float y, |
|||
out float dz, |
|||
out float dx) |
|||
{ |
|||
// Y is the green primary; Dz and Dx are half-scaled blue and red differences.
|
|||
y = g; |
|||
dz = ((BlueNormalization * b) - y) * 0.5F; |
|||
dx = (r - (RedGreenContribution * y)) * 0.5F; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> r, |
|||
Vector128<float> g, |
|||
Vector128<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> y, |
|||
out Vector128<float> dz, |
|||
out Vector128<float> dx) |
|||
{ |
|||
y = g; |
|||
dz = Vector128.Create(0.5F) * Vector128.MultiplyAddEstimate(Vector128.Create(BlueNormalization), b, -y); |
|||
dx = Vector128.Create(0.5F) * Vector128.MultiplyAddEstimate(Vector128.Create(-RedGreenContribution), y, r); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> r, |
|||
Vector256<float> g, |
|||
Vector256<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> y, |
|||
out Vector256<float> dz, |
|||
out Vector256<float> dx) |
|||
{ |
|||
y = g; |
|||
dz = Vector256.Create(0.5F) * Vector256.MultiplyAddEstimate(Vector256.Create(BlueNormalization), b, -y); |
|||
dx = Vector256.Create(0.5F) * Vector256.MultiplyAddEstimate(Vector256.Create(-RedGreenContribution), y, r); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> r, |
|||
Vector512<float> g, |
|||
Vector512<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> y, |
|||
out Vector512<float> dz, |
|||
out Vector512<float> dx) |
|||
{ |
|||
y = g; |
|||
dz = Vector512.Create(0.5F) * Vector512.MultiplyAddEstimate(Vector512.Create(BlueNormalization), b, -y); |
|||
dx = Vector512.Create(0.5F) * Vector512.MultiplyAddEstimate(Vector512.Create(-RedGreenContribution), y, r); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,141 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.Intrinsics; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Implements YCgCo conversion for scalar and SIMD lanes.
|
|||
/// </summary>
|
|||
internal readonly struct Av1YCgCoColorOperator : IAv1ColorOperator |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public static bool ChromaUsesLumaRange => false; |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref float y, ref float cg, ref float co, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
// Reusing Y - Cg for both outer primaries keeps the inverse transform to four additions.
|
|||
float temporary = y - cg; |
|||
float r = temporary + co; |
|||
float g = y + cg; |
|||
float b = temporary - co; |
|||
y = r; |
|||
cg = g; |
|||
co = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector128<float> y, ref Vector128<float> cg, ref Vector128<float> co, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector128<float> temporary = y - cg; |
|||
Vector128<float> r = temporary + co; |
|||
Vector128<float> g = y + cg; |
|||
Vector128<float> b = temporary - co; |
|||
y = r; |
|||
cg = g; |
|||
co = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector256<float> y, ref Vector256<float> cg, ref Vector256<float> co, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector256<float> temporary = y - cg; |
|||
Vector256<float> r = temporary + co; |
|||
Vector256<float> g = y + cg; |
|||
Vector256<float> b = temporary - co; |
|||
y = r; |
|||
cg = g; |
|||
co = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertToRgb(ref Vector512<float> y, ref Vector512<float> cg, ref Vector512<float> co, in Av1ColorConversionParameters parameters) |
|||
{ |
|||
Vector512<float> temporary = y - cg; |
|||
Vector512<float> r = temporary + co; |
|||
Vector512<float> g = y + cg; |
|||
Vector512<float> b = temporary - co; |
|||
y = r; |
|||
cg = g; |
|||
co = b; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
float r, |
|||
float g, |
|||
float b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out float y, |
|||
out float cg, |
|||
out float co) |
|||
{ |
|||
// R + B is shared by Y and Cg, while Co is the half-scaled red/blue difference.
|
|||
float sum = r + b; |
|||
y = (0.5F * g) + (0.25F * sum); |
|||
cg = (0.5F * g) - (0.25F * sum); |
|||
co = 0.5F * (r - b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector128<float> r, |
|||
Vector128<float> g, |
|||
Vector128<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector128<float> y, |
|||
out Vector128<float> cg, |
|||
out Vector128<float> co) |
|||
{ |
|||
Vector128<float> sum = r + b; |
|||
y = Vector128.MultiplyAddEstimate(Vector128.Create(0.5F), g, Vector128.Create(0.25F) * sum); |
|||
cg = Vector128.MultiplyAddEstimate(Vector128.Create(0.5F), g, Vector128.Create(-0.25F) * sum); |
|||
co = Vector128.Create(0.5F) * (r - b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector256<float> r, |
|||
Vector256<float> g, |
|||
Vector256<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector256<float> y, |
|||
out Vector256<float> cg, |
|||
out Vector256<float> co) |
|||
{ |
|||
Vector256<float> sum = r + b; |
|||
y = Vector256.MultiplyAddEstimate(Vector256.Create(0.5F), g, Vector256.Create(0.25F) * sum); |
|||
cg = Vector256.MultiplyAddEstimate(Vector256.Create(0.5F), g, Vector256.Create(-0.25F) * sum); |
|||
co = Vector256.Create(0.5F) * (r - b); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void ConvertFromRgb( |
|||
Vector512<float> r, |
|||
Vector512<float> g, |
|||
Vector512<float> b, |
|||
in Av1ColorConversionParameters parameters, |
|||
out Vector512<float> y, |
|||
out Vector512<float> cg, |
|||
out Vector512<float> co) |
|||
{ |
|||
Vector512<float> sum = r + b; |
|||
y = Vector512.MultiplyAddEstimate(Vector512.Create(0.5F), g, Vector512.Create(0.25F) * sum); |
|||
cg = Vector512.MultiplyAddEstimate(Vector512.Create(0.5F), g, Vector512.Create(-0.25F) * sum); |
|||
co = Vector512.Create(0.5F) * (r - b); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
|
|||
/// <summary>
|
|||
/// Identifies the H.273 matrix operation used between encoded planes and RGB components.
|
|||
/// </summary>
|
|||
internal enum Av1ColorConversionMode |
|||
{ |
|||
/// <summary>
|
|||
/// A coefficient-based YCbCr matrix conversion.
|
|||
/// </summary>
|
|||
Coefficients, |
|||
|
|||
/// <summary>
|
|||
/// Direct G, B, and R component mapping from the Y, U, and V planes.
|
|||
/// </summary>
|
|||
Identity, |
|||
|
|||
/// <summary>
|
|||
/// The reversible-style YCgCo color transform.
|
|||
/// </summary>
|
|||
YCgCo, |
|||
|
|||
/// <summary>
|
|||
/// The SMPTE ST 2085 YDzDx color transform.
|
|||
/// </summary>
|
|||
Smpte2085, |
|||
|
|||
/// <summary>
|
|||
/// A constant-luminance transform using the signaled transfer characteristics.
|
|||
/// </summary>
|
|||
ConstantLuminance, |
|||
|
|||
/// <summary>
|
|||
/// The BT.2100 ICtCp color transform.
|
|||
/// </summary>
|
|||
ICtCp, |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts normalized component planes between an AV1 color model and RGB.
|
|||
/// </summary>
|
|||
internal abstract partial class Av1ColorConverterBase |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Av1ColorConverterBase"/> class.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="isMonochrome">Whether the frame contains only luma samples.</param>
|
|||
protected Av1ColorConverterBase(in Av1ColorConversionParameters parameters, bool isMonochrome) |
|||
{ |
|||
this.Parameters = parameters; |
|||
this.IsMonochrome = isMonochrome; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the resolved H.273 conversion parameters.
|
|||
/// </summary>
|
|||
protected Av1ColorConversionParameters Parameters { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the frame contains only luma samples.
|
|||
/// </summary>
|
|||
protected bool IsMonochrome { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the scale used to encode luma components.
|
|||
/// </summary>
|
|||
public float LumaScale => this.Parameters.LumaScale; |
|||
|
|||
/// <summary>
|
|||
/// Gets the bias used to encode luma components.
|
|||
/// </summary>
|
|||
public float LumaBias => this.Parameters.LumaBias; |
|||
|
|||
/// <summary>
|
|||
/// Gets the scale used to encode chroma components.
|
|||
/// </summary>
|
|||
public abstract float ChromaScale { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bias used to encode chroma components.
|
|||
/// </summary>
|
|||
public abstract float ChromaBias { get; } |
|||
|
|||
/// <summary>
|
|||
/// Converts normalized AV1 components to normalized RGB in place.
|
|||
/// </summary>
|
|||
/// <param name="component0">The luma or first color component, replaced by red.</param>
|
|||
/// <param name="component1">The first chroma or second color component, replaced by green.</param>
|
|||
/// <param name="component2">The second chroma or third color component, replaced by blue.</param>
|
|||
public abstract void ConvertToRgbInPlace(Span<float> component0, Span<float> component1, Span<float> component2); |
|||
|
|||
/// <summary>
|
|||
/// Converts normalized RGB components to normalized AV1 components in place.
|
|||
/// </summary>
|
|||
/// <param name="component0">The red component, replaced by luma or the first color component.</param>
|
|||
/// <param name="component1">The green component, replaced by the first chroma or second color component.</param>
|
|||
/// <param name="component2">The blue component, replaced by the second chroma or third color component.</param>
|
|||
/// <param name="maximumValue">The largest value in the RGB component planes.</param>
|
|||
public abstract void ConvertFromRgbInPlace( |
|||
Span<float> component0, |
|||
Span<float> component1, |
|||
Span<float> component2, |
|||
float maximumValue); |
|||
|
|||
/// <summary>
|
|||
/// Creates the converter selected by the resolved H.273 matrix and transfer characteristics.
|
|||
/// </summary>
|
|||
/// <param name="mode">The resolved matrix conversion mode.</param>
|
|||
/// <param name="parameters">The resolved H.273 conversion parameters.</param>
|
|||
/// <param name="isMonochrome">Whether the frame contains only luma samples.</param>
|
|||
/// <returns>The selected converter.</returns>
|
|||
public static Av1ColorConverterBase Create(Av1ColorConversionMode mode, in Av1ColorConversionParameters parameters, bool isMonochrome) |
|||
=> mode switch |
|||
{ |
|||
Av1ColorConversionMode.Identity => new Av1ColorConverter<Av1IdentityColorOperator>(in parameters, isMonochrome), |
|||
Av1ColorConversionMode.YCgCo => new Av1ColorConverter<Av1YCgCoColorOperator>(in parameters, isMonochrome), |
|||
Av1ColorConversionMode.Smpte2085 => new Av1ColorConverter<Av1Smpte2085ColorOperator>(in parameters, isMonochrome), |
|||
Av1ColorConversionMode.ConstantLuminance => new Av1ColorConverter<Av1ConstantLuminanceColorOperator>(in parameters, isMonochrome), |
|||
Av1ColorConversionMode.ICtCp => new Av1ColorConverter<Av1ICtCpColorOperator>(in parameters, isMonochrome), |
|||
_ => new Av1ColorConverter<Av1CoefficientColorOperator>(in parameters, isMonochrome), |
|||
}; |
|||
} |
|||
@ -1,623 +0,0 @@ |
|||
// 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,426 @@ |
|||
// 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 row converters used by AV1 color conversion.
|
|||
/// </content>
|
|||
internal static partial class Av1YuvConverter |
|||
{ |
|||
/// <summary>
|
|||
/// Converts one reconstructed AV1 row using frame-scoped 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 YuvToRgbRowConverter<TPixel, TSample, TLoader> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TSample : unmanaged |
|||
where TLoader : struct, ISampleLoader<TSample> |
|||
{ |
|||
/// <summary>
|
|||
/// The configuration used by bulk pixel conversion.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The reconstructed AV1 frame containing the source planes.
|
|||
/// </summary>
|
|||
private readonly Av1FrameBuffer<byte> frameBuffer; |
|||
|
|||
/// <summary>
|
|||
/// The destination image frame.
|
|||
/// </summary>
|
|||
private readonly ImageFrame<TPixel> image; |
|||
|
|||
/// <summary>
|
|||
/// The full-resolution luma plane.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> yPlane; |
|||
|
|||
/// <summary>
|
|||
/// The blue-difference plane when the frame contains chroma.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> uPlane; |
|||
|
|||
/// <summary>
|
|||
/// The red-difference plane when the frame contains chroma.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> vPlane; |
|||
|
|||
/// <summary>
|
|||
/// The frame-scoped color-model converter.
|
|||
/// </summary>
|
|||
private readonly Av1ColorConverterBase colorConverter; |
|||
|
|||
/// <summary>
|
|||
/// The signaled chroma sample position used for reconstruction.
|
|||
/// </summary>
|
|||
private readonly ObuChromoSamplePosition chromaSamplePosition; |
|||
|
|||
/// <summary>
|
|||
/// Whether the source contains only a luma plane.
|
|||
/// </summary>
|
|||
private readonly bool isMonochrome; |
|||
|
|||
/// <summary>
|
|||
/// The horizontal chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int subX; |
|||
|
|||
/// <summary>
|
|||
/// The vertical chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int subY; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="YuvToRgbRowConverter{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="colorConverter">The selected H.273 color converter.</param>
|
|||
public YuvToRgbRowConverter( |
|||
Configuration configuration, |
|||
Av1FrameBuffer<byte> frameBuffer, |
|||
ImageFrame<TPixel> image, |
|||
Av1ColorConverterBase colorConverter) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.frameBuffer = frameBuffer; |
|||
this.image = image; |
|||
this.colorConverter = colorConverter; |
|||
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); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of float elements required by the reusable conversion buffer.
|
|||
/// </summary>
|
|||
public int BufferLength |
|||
{ |
|||
get |
|||
{ |
|||
// 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 this.image.Width * (rowCount + packedRowCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one reconstructed AV1 row to packed pixels.
|
|||
/// </summary>
|
|||
/// <param name="y">The row index.</param>
|
|||
/// <param name="span">The reusable conversion buffer.</param>
|
|||
public void Convert(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; |
|||
} |
|||
|
|||
this.colorConverter.ConvertToRgbInPlace(red, green, blue); |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one source row, or one vertically subsampled row pair, using reusable pooled component storage.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The source pixel type.</typeparam>
|
|||
/// <typeparam name="TSample">The encoded sample type.</typeparam>
|
|||
/// <typeparam name="TStorer">The SIMD narrowing and storage operations for the sample type.</typeparam>
|
|||
private readonly struct RgbToYuvRowConverter<TPixel, TSample, TStorer> |
|||
where TPixel : unmanaged, IPixel<TPixel> |
|||
where TSample : unmanaged |
|||
where TStorer : struct, ISampleStorer<TSample> |
|||
{ |
|||
/// <summary>
|
|||
/// The configuration used by bulk pixel conversion.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// The destination AV1 frame.
|
|||
/// </summary>
|
|||
private readonly Av1FrameBuffer<byte> frameBuffer; |
|||
|
|||
/// <summary>
|
|||
/// The source image frame.
|
|||
/// </summary>
|
|||
private readonly ImageFrame<TPixel> image; |
|||
|
|||
/// <summary>
|
|||
/// The full-resolution luma plane.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> yPlane; |
|||
|
|||
/// <summary>
|
|||
/// The blue-difference plane when the frame contains chroma.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> uPlane; |
|||
|
|||
/// <summary>
|
|||
/// The red-difference plane when the frame contains chroma.
|
|||
/// </summary>
|
|||
private readonly Buffer2DRegion<byte> vPlane; |
|||
|
|||
/// <summary>
|
|||
/// The frame-scoped color-model converter.
|
|||
/// </summary>
|
|||
private readonly Av1ColorConverterBase colorConverter; |
|||
|
|||
/// <summary>
|
|||
/// The largest value represented by the encoded AV1 bit depth.
|
|||
/// </summary>
|
|||
private readonly float sampleMaximum; |
|||
|
|||
/// <summary>
|
|||
/// Whether the destination contains only a luma plane.
|
|||
/// </summary>
|
|||
private readonly bool isMonochrome; |
|||
|
|||
/// <summary>
|
|||
/// The horizontal chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int subX; |
|||
|
|||
/// <summary>
|
|||
/// The vertical chroma subsampling shift.
|
|||
/// </summary>
|
|||
private readonly int rowShift; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RgbToYuvRowConverter{TPixel, TSample, TStorer}"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration used for packed-pixel conversion.</param>
|
|||
/// <param name="frameBuffer">The destination AV1 frame.</param>
|
|||
/// <param name="image">The source image frame.</param>
|
|||
/// <param name="colorConverter">The selected H.273 color converter.</param>
|
|||
/// <param name="sampleMaximum">The largest encoded sample value.</param>
|
|||
public RgbToYuvRowConverter( |
|||
Configuration configuration, |
|||
Av1FrameBuffer<byte> frameBuffer, |
|||
ImageFrame<TPixel> image, |
|||
Av1ColorConverterBase colorConverter, |
|||
float sampleMaximum) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.frameBuffer = frameBuffer; |
|||
this.image = image; |
|||
this.colorConverter = colorConverter; |
|||
this.sampleMaximum = sampleMaximum; |
|||
this.isMonochrome = frameBuffer.ColorFormat == Av1ColorFormat.Yuv400; |
|||
this.subX = frameBuffer.ColorConfig.SubSamplingX ? 1 : 0; |
|||
this.rowShift = !this.isMonochrome && frameBuffer.ColorConfig.SubSamplingY ? 1 : 0; |
|||
this.yPlane = frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0); |
|||
this.uPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.U, this.subX, this.rowShift); |
|||
this.vPlane = this.isMonochrome ? default : frameBuffer.DeriveBlockPointer(Av1Plane.V, this.subX, this.rowShift); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of float elements required by the reusable conversion buffer.
|
|||
/// </summary>
|
|||
public int ComponentBufferLength |
|||
{ |
|||
get |
|||
{ |
|||
// Three planar rows hold the converted YUV values, and 4:2:0 keeps a second set until its
|
|||
// chroma has been averaged with the first.
|
|||
int componentRowCount = this.rowShift == 0 ? 3 : 6; |
|||
return this.image.Width * componentRowCount; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one source row, or one vertically subsampled row pair, to AV1 planes.
|
|||
/// </summary>
|
|||
/// <param name="y">The conversion iteration index.</param>
|
|||
/// <param name="packed">The reusable high-bit-depth RGB staging row.</param>
|
|||
/// <param name="components">The reusable planar component buffer.</param>
|
|||
public void Convert(int y, Span<Rgb48> packed, Span<float> components) |
|||
{ |
|||
int width = this.image.Width; |
|||
int sourceY = y << this.rowShift; |
|||
Span<float> yRow0 = components[..width]; |
|||
Span<float> cbRow0 = components.Slice(width, width); |
|||
Span<float> crRow0 = components.Slice(width * 2, width); |
|||
this.ConvertSourceRow(sourceY, packed, yRow0, cbRow0, crRow0); |
|||
WriteSamples<TSample, TStorer>( |
|||
yRow0, |
|||
this.GetPlaneRow(Av1Plane.Y, sourceY), |
|||
this.colorConverter.LumaScale, |
|||
this.colorConverter.LumaBias, |
|||
this.sampleMaximum); |
|||
|
|||
bool hasSecondSourceRow = this.rowShift != 0 && sourceY + 1 < this.image.Height; |
|||
Span<float> yRow1 = Span<float>.Empty; |
|||
Span<float> cbRow1 = Span<float>.Empty; |
|||
Span<float> crRow1 = Span<float>.Empty; |
|||
if (hasSecondSourceRow) |
|||
{ |
|||
yRow1 = components.Slice(width * 3, width); |
|||
cbRow1 = components.Slice(width * 4, width); |
|||
crRow1 = components.Slice(width * 5, width); |
|||
this.ConvertSourceRow(sourceY + 1, packed, yRow1, cbRow1, crRow1); |
|||
WriteSamples<TSample, TStorer>( |
|||
yRow1, |
|||
this.GetPlaneRow(Av1Plane.Y, sourceY + 1), |
|||
this.colorConverter.LumaScale, |
|||
this.colorConverter.LumaBias, |
|||
this.sampleMaximum); |
|||
} |
|||
|
|||
if (this.isMonochrome) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Span<TSample> uDestination = this.GetPlaneRow(Av1Plane.U, y); |
|||
Span<TSample> vDestination = this.GetPlaneRow(Av1Plane.V, y); |
|||
float chromaScale = this.colorConverter.ChromaScale; |
|||
float chromaBias = this.colorConverter.ChromaBias; |
|||
if (this.subX == 0) |
|||
{ |
|||
WriteSamples<TSample, TStorer>(cbRow0, uDestination, chromaScale, chromaBias, this.sampleMaximum); |
|||
WriteSamples<TSample, TStorer>(crRow0, vDestination, chromaScale, chromaBias, this.sampleMaximum); |
|||
} |
|||
else |
|||
{ |
|||
WriteSubsampledSamples<TSample, TStorer>(cbRow0, cbRow1, uDestination, chromaScale, chromaBias, this.sampleMaximum); |
|||
WriteSubsampledSamples<TSample, TStorer>(crRow0, crRow1, vDestination, chromaScale, chromaBias, this.sampleMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts one source row into normalized planar AV1 components.
|
|||
/// </summary>
|
|||
/// <param name="y">The source row index.</param>
|
|||
/// <param name="packed">The high-bit-depth RGB staging row.</param>
|
|||
/// <param name="luma">The destination luma values.</param>
|
|||
/// <param name="chromaBlue">The destination blue-difference values.</param>
|
|||
/// <param name="chromaRed">The destination red-difference values.</param>
|
|||
private void ConvertSourceRow(int y, Span<Rgb48> packed, Span<float> luma, Span<float> chromaBlue, Span<float> chromaRed) |
|||
{ |
|||
ReadOnlySpan<TPixel> source = this.image.PixelBuffer.DangerousGetRowSpan(y); |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
// This is the same planar input contract used by JPEG encoding. Pixel types with optimized
|
|||
// unpackers reach their existing SIMD path before the AV1 operator consumes the planes.
|
|||
PixelOperations<TPixel>.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source); |
|||
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum); |
|||
} |
|||
else |
|||
{ |
|||
// JPEG's planar unpack contract is eight-bit. AV1 10/12-bit encoding stages Rgb48 instead
|
|||
// so high-precision source pixels are not truncated before the color transform.
|
|||
PixelOperations<TPixel>.Instance.ToRgb48(this.configuration, source, packed); |
|||
DeinterleaveRgb48(packed, luma, chromaBlue, chromaRed); |
|||
this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, UShortMaximum); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a writable row from an eight-bit or high-bit-depth AV1 plane.
|
|||
/// </summary>
|
|||
/// <param name="plane">The requested plane.</param>
|
|||
/// <param name="y">The row index in the requested plane.</param>
|
|||
/// <returns>The writable sample row.</returns>
|
|||
private Span<TSample> GetPlaneRow(Av1Plane plane, int y) |
|||
{ |
|||
if (typeof(TSample) == typeof(byte)) |
|||
{ |
|||
Buffer2DRegion<byte> region = plane switch |
|||
{ |
|||
Av1Plane.Y => this.yPlane, |
|||
Av1Plane.U => this.uPlane, |
|||
_ => this.vPlane, |
|||
}; |
|||
|
|||
return MemoryMarshal.Cast<byte, TSample>(region.DangerousGetRowSpan(y)); |
|||
} |
|||
|
|||
int subX = plane == Av1Plane.Y ? 0 : this.subX; |
|||
int subY = plane == Av1Plane.Y ? 0 : this.rowShift; |
|||
return MemoryMarshal.Cast<ushort, TSample>(this.frameBuffer.GetHighBitDepthRowSpan(plane, y, subX, subY)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,156 +0,0 @@ |
|||
// 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,129 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Six Labors Split License.
|
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1; |
|||
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; |
|||
|
|||
/// <summary>
|
|||
/// Measures frame-wide AV1 YUV 4:2:0 color conversion in both directions.
|
|||
/// </summary>
|
|||
[MemoryDiagnoser(displayGenColumns: false)] |
|||
public class Av1ColorConversionBenchmarks |
|||
{ |
|||
/// <summary>
|
|||
/// The benchmark frame width.
|
|||
/// </summary>
|
|||
private const int Width = 1920; |
|||
|
|||
/// <summary>
|
|||
/// The benchmark frame height.
|
|||
/// </summary>
|
|||
private const int Height = 1080; |
|||
|
|||
/// <summary>
|
|||
/// The source RGB image.
|
|||
/// </summary>
|
|||
private Image<Rgb48> source = null!; |
|||
|
|||
/// <summary>
|
|||
/// The destination RGB image.
|
|||
/// </summary>
|
|||
private Image<Rgb48> destination = null!; |
|||
|
|||
/// <summary>
|
|||
/// The reusable AV1 frame planes.
|
|||
/// </summary>
|
|||
private Av1FrameBuffer<byte> frameBuffer = null!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the encoded AV1 bit depth.
|
|||
/// </summary>
|
|||
[Params(8, 10, 12)] |
|||
public int BitDepth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Allocates and populates deterministic full-HD RGB and YUV frames outside the measured operations.
|
|||
/// </summary>
|
|||
[GlobalSetup] |
|||
public void Setup() |
|||
{ |
|||
this.source = new Image<Rgb48>(Width, Height); |
|||
this.destination = new Image<Rgb48>(Width, Height); |
|||
for (int y = 0; y < Height; y++) |
|||
{ |
|||
Span<Rgb48> row = this.source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); |
|||
for (int x = 0; x < Width; x++) |
|||
{ |
|||
// The relatively prime channel steps avoid uniform rows while remaining deterministic.
|
|||
row[x] = new Rgb48( |
|||
(ushort)((x * 1879) + (y * 791)), |
|||
(ushort)((x * 977) + (y * 3251)), |
|||
(ushort)((x * 613) + (y * 4987))); |
|||
} |
|||
} |
|||
|
|||
ObuSequenceHeader sequenceHeader = new() |
|||
{ |
|||
MaxFrameWidth = Width, |
|||
MaxFrameHeight = Height, |
|||
ColorConfig = new ObuColorConfig |
|||
{ |
|||
BitDepth = this.BitDepth switch |
|||
{ |
|||
10 => Av1BitDepth.TenBit, |
|||
12 => Av1BitDepth.TwelveBit, |
|||
_ => Av1BitDepth.EightBit, |
|||
}, |
|||
ColorPrimaries = ObuColorPrimaries.Bt709, |
|||
TransferCharacteristics = ObuTransferCharacteristics.Bt709, |
|||
MatrixCoefficients = ObuMatrixCoefficients.Bt709, |
|||
ColorRange = false, |
|||
SubSamplingX = true, |
|||
SubSamplingY = true, |
|||
ChromaSamplePosition = ObuChromoSamplePosition.Unknown, |
|||
}, |
|||
}; |
|||
|
|||
this.frameBuffer = new Av1FrameBuffer<byte>(Configuration.Default, sequenceHeader, Av1ColorFormat.Yuv420, false); |
|||
Av1YuvConverter.ConvertFromRgb(Configuration.Default, this.source.Frames.RootFrame, this.frameBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Releases the benchmark images and reconstructed planes.
|
|||
/// </summary>
|
|||
[GlobalCleanup] |
|||
public void Cleanup() |
|||
{ |
|||
this.frameBuffer.Dispose(); |
|||
this.destination.Dispose(); |
|||
this.source.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures full-frame YUV-to-RGB conversion, including chroma reconstruction and packed-pixel conversion.
|
|||
/// </summary>
|
|||
/// <returns>A converted pixel that keeps the frame result observable.</returns>
|
|||
[Benchmark] |
|||
public Rgb48 ConvertToRgb() |
|||
{ |
|||
Av1YuvConverter.ConvertToRgb(Configuration.Default, this.frameBuffer, this.destination.Frames.RootFrame); |
|||
return this.destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(Height - 1)[Width - 1]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Measures full-frame RGB-to-YUV conversion, including planar unpacking and chroma downsampling.
|
|||
/// </summary>
|
|||
/// <returns>An encoded luma sample that keeps the frame result observable.</returns>
|
|||
[Benchmark] |
|||
public int ConvertFromRgb() |
|||
{ |
|||
Av1YuvConverter.ConvertFromRgb(Configuration.Default, this.source.Frames.RootFrame, this.frameBuffer); |
|||
return this.BitDepth == 8 |
|||
? this.frameBuffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(Height - 1)[Width - 1] |
|||
: this.frameBuffer.GetHighBitDepthRowSpan(Av1Plane.Y, Height - 1, 0, 0)[Width - 1]; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue