diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 29a47001f..aa8c30dcc 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -34,12 +34,19 @@ Checkboxes may be marked complete only when the implementation and the verificat
- [x] Preserve the exact ICC payload from an independently encoded AVIF primary item.
- [x] Prove that a genuine non-sRGB AVIF profile changes decoded pixels and matches the independently converted source image within the documented AV1 tolerance.
- [x] Verify canonical-sRGB compaction and metadata skipping independently from ICC preservation and color conversion.
+ - [x] Replace the AV1 RGB/YUV arithmetic layer with JPEG's closed-generic static operator pattern in both directions: frame-scoped operator selection, in-place planar decode, RGB inputs with `out` component planes for encode, `Vector512`/`Vector256`/`Vector128` traversal, and one scalar tail.
+ - [x] Keep codec row scheduling sequential and reuse frame-scoped allocator-backed buffers. Reuse JPEG's optimized `UnpackIntoRgbPlanes` contract for 8-bit encoding and pooled `Rgb48` staging for 10/12-bit encoding so high-precision input is not truncated through an eight-bit or `Vector4` intermediate.
+ - [x] Implement SIMD range expansion, chroma reconstruction, matrix/transfer conversion, clamping, and RGB packing for coefficient YCbCr, identity GBR, YCgCo, constant luminance, SMPTE ST 2085, and PQ/HLG ICtCp, with focused SIMD-lane/scalar-tail parity coverage.
+ - [x] Implement SIMD 4:2:0 and 4:2:2 encoder downsampling with odd-width and odd-height tails, and verify the stored 8/12-bit chroma samples against independently encoded full-resolution planes.
- [ ] Complete the SIMD YUV/CICP paths for every supported AV1 bit depth, chroma format, range, matrix, transfer function, color primary, and chroma position, with scalar fallback only when hardware vectorization is unavailable or the operation is inherently non-vectorizable.
- [x] Apply ICC conversion only after the SIMD YUV/CICP stage, alpha composition, grid assembly, and presentation transforms have produced the presented RGB image; retain ImageSharp's shared ICC converter and optimize reusable bulk kernels rather than creating a HEIF-specific color-management implementation.
- [ ] Verify ICC preservation, conversion, compaction, and metadata skipping for grids, alpha-composited images, every presented sequence frame, and the completed HEVC path.
- [x] Independently encode AVIF primary, grid, alpha-auxiliary, and two-frame sequence vectors with libavif 1.4.2/libaom 3.14.1; verify exact profile preservation, non-sRGB conversion, canonical-sRGB compaction, non-sRGB compact preservation, metadata skipping, every presented frame, and unchanged composed alpha.
- [ ] Repeat the same matrix for HEVC primary, grid, alpha-auxiliary, and sequence paths after HEVC reconstruction is complete.
- [ ] Benchmark representative 8/10/12-bit end-to-end AVIF and HEIC color pipelines with and without ICC conversion, recording absolute timings and allocations before marking Phase 5 complete.
+ - [x] Add permanent frame-wide `Av1ColorConversionBenchmarks` coverage for sequential 1920x1080 YUV 4:2:0 conversion in both directions at 8, 10, and 12 bits, including managed-allocation reporting.
+ - [x] Record the .NET 10 short-run AV1 baseline on 2026-08-25. SIMD decode measured 4.025/2.803/2.742 ms and encode measured 3.750/2.555/2.450 ms at 8/10/12 bits with 488-744 B allocated per frame. Forced-scalar decode measured 31.15/29.47/28.40 ms and encode measured 19.19/15.77/15.14 ms, making the normal SIMD pipeline 7.7-10.5 times faster for decode and 5.1-6.2 times faster for encode on this machine.
+ - [ ] Add representative ICC-enabled AVIF cases and the equivalent HEVC cases after the HEVC reconstruction path is complete.
- [x] **Completed:** remove production `ToArray()` materializations from the HEIF implementation and make every retained copy an explicit ownership decision.
- [x] Store AV1 palette colors and HEVC short-term reference-picture state inline at their normative fixed bounds.
- [x] Retain only the AV1 configuration sequence-header payload required for item/sample equivalence instead of every configuration OBU.
@@ -458,9 +465,17 @@ Exit gate:
Tasks:
- [ ] Implement monochrome, 4:2:0, 4:2:2, and 4:4:4 plane access with every valid signaled chroma sample position.
+ - [x] Implement SIMD AV1 4:2:0 and 4:2:2 chroma reconstruction and downsampling, including centered/colocated reconstruction and odd image edges.
+ - [ ] Complete the HEVC plane paths and the exhaustive AV1/HEVC chroma-position matrix before closing this item.
- [ ] Implement full- and limited-range expansion for 8, 10, and 12-bit samples across every supported plane layout.
+ - [x] Implement AV1 range expansion and quantization with SIMD-first byte/ushort loaders and storers and pooled planar rows.
+ - [ ] Complete the equivalent HEVC paths and independently verify the full cross-product.
- [ ] Implement every non-reserved HEVC/AV1 color-primary, transfer-characteristic, and matrix-coefficient signaling path, including identity conversion, with correct fixed-point rounding and clipping.
+ - [x] Implement the AV1 H.273 matrix and transfer-function operators in both directions, including identity, derived coefficients, constant luminance, SMPTE ST 2085, and PQ/HLG ICtCp.
+ - [ ] Add independent conformance vectors for signaling combinations that libavif deliberately rejects, then complete and verify the HEVC operator set.
- [ ] Make SIMD the default decode path for YUV range expansion, chroma reconstruction, H.273 matrix and transfer conversion, clamping, and 8/10/12-bit RGB packing. Use the established static operator pattern with `Vector512`, `Vector256`, and `Vector128` dispatch followed by one scalar tail that remains the behavioral oracle.
+ - [x] AV1 uses the JPEG-style static operator contract and descending SIMD-width traversal for decode and encode, with scalar execution limited to the remaining lanes or hardware without vector support.
+ - [ ] Complete the same default SIMD pipeline for HEVC and record end-to-end measurements for both codecs.
- [ ] Apply embedded ICC profiles after codec YUV/CICP conversion and image composition through ImageSharp's existing color-profile converter. Cover preserve, convert, compact, and ignore behavior for primary items, grids, alpha-composited images, and every presented sequence frame; never substitute CICP or an assumed sRGB profile for an embedded ICC profile.
- Primary AVIF verification passes four independently scoped cases: exact profile preservation, non-sRGB conversion with changed pixels and paired-source comparison, canonical-sRGB compaction without pixel changes, and metadata skipping. Grid, auxiliary-alpha, sequence, and HEVC cases remain open.
- [ ] Decode alpha auxiliary items as monochrome planes, validate dimensions and bit depth, and compose them without losing precision. Define premultiplication behavior from AVIF signaling and ImageSharp's pixel contract.
@@ -479,6 +494,8 @@ Delete the JPEG payload path from the production encoder. Keep the synchronous I
Implement in vertical slices that always produce a decodable AV1 bitstream:
- [ ] Convert RGB/RGBA to AV1 planes for every 8, 10, and 12-bit output, range, matrix, and monochrome/4:2:0/4:2:2/4:4:4 combination permitted by the selected AV1 profile.
+ - [x] Implement the pooled sequential RGB-to-planar pipeline, 8/10/12-bit quantization, every implemented H.273 operator, and SIMD 4:2:0/4:2:2 downsampling with odd-edge handling.
+ - [ ] Complete the exhaustive format/range/matrix/transfer/primary cross-product and validate encoded planes against an independent implementation before closing this item.
- [ ] Write sequence, frame, tile-group, and metadata OBUs for a reduced still picture.
- [ ] Implement a temporary smallest-valid intra-only vertical slice using existing partition, prediction, transform, quantization, coefficient, and entropy structures.
- [ ] Complete block geometry, neighbor/context updates, transform selection and forward transforms, quantization, coefficient tokenization, and range coding.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs
new file mode 100644
index 000000000..e2581248e
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConversionParameters.cs
@@ -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;
+
+///
+/// Stores the resolved H.273 values shared by every scalar and SIMD lane.
+///
+internal readonly struct Av1ColorConversionParameters
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The red luma coefficient.
+ /// The green luma coefficient.
+ /// The blue luma coefficient.
+ /// The signaled transfer characteristics.
+ /// The constant-luminance chroma scales.
+ /// The encoded luma bias.
+ /// The encoded luma range.
+ /// The encoded chroma midpoint.
+ /// The encoded chroma range.
+ 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;
+ }
+
+ ///
+ /// Gets the red luma coefficient.
+ ///
+ public float Kr { get; }
+
+ ///
+ /// Gets the green luma coefficient.
+ ///
+ public float Kg { get; }
+
+ ///
+ /// Gets the blue luma coefficient.
+ ///
+ public float Kb { get; }
+
+ ///
+ /// Gets the red contribution from the red-difference component.
+ ///
+ public float RedChromaScale { get; }
+
+ ///
+ /// Gets the blue contribution from the blue-difference component.
+ ///
+ public float BlueChromaScale { get; }
+
+ ///
+ /// Gets the red-difference subtraction from green.
+ ///
+ public float GreenRedChromaScale { get; }
+
+ ///
+ /// Gets the blue-difference subtraction from green.
+ ///
+ public float GreenBlueChromaScale { get; }
+
+ ///
+ /// Gets the signaled transfer characteristics.
+ ///
+ public ObuTransferCharacteristics TransferCharacteristics { get; }
+
+ ///
+ /// Gets the constant-luminance chroma scales.
+ ///
+ public Av1ConstantLuminanceScales ConstantLuminanceScales { get; }
+
+ ///
+ /// Gets the encoded luma bias.
+ ///
+ public float LumaBias { get; }
+
+ ///
+ /// Gets the encoded luma range.
+ ///
+ public float LumaScale { get; }
+
+ ///
+ /// Gets the encoded chroma midpoint.
+ ///
+ public float ChromaBias { get; }
+
+ ///
+ /// Gets the encoded chroma range.
+ ///
+ public float ChromaScale { get; }
+}
+
+///
+/// Stores the H.273 chroma normalization constants for constant-luminance conversion.
+///
+internal readonly struct Av1ConstantLuminanceScales
+{
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The signaled transfer characteristics.
+ /// The red luma coefficient.
+ /// The blue luma coefficient.
+ 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);
+ }
+
+ ///
+ /// Gets the negative blue-difference scale.
+ ///
+ public float NegativeBlue { get; }
+
+ ///
+ /// Gets the positive blue-difference scale.
+ ///
+ public float PositiveBlue { get; }
+
+ ///
+ /// Gets the negative red-difference scale.
+ ///
+ public float NegativeRed { get; }
+
+ ///
+ /// Gets the positive red-difference scale.
+ ///
+ public float PositiveRed { get; }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs
new file mode 100644
index 000000000..8d92ee7ad
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.CoefficientOperator.cs
@@ -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
+{
+ ///
+ /// Implements coefficient-based YCbCr conversion for scalar and SIMD lanes.
+ ///
+ internal readonly struct Av1CoefficientColorOperator : IAv1ColorOperator
+ {
+ ///
+ public static bool ChromaUsesLumaRange => false;
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in Av1ColorConversionParameters parameters)
+ {
+ Vector128 r = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.RedChromaScale), cr, y);
+ Vector128 g = Vector128.MultiplyAddEstimate(
+ Vector128.Create(-parameters.GreenBlueChromaScale),
+ cb,
+ Vector128.MultiplyAddEstimate(Vector128.Create(-parameters.GreenRedChromaScale), cr, y));
+ Vector128 b = Vector128.MultiplyAddEstimate(Vector128.Create(parameters.BlueChromaScale), cb, y);
+
+ y = r;
+ cb = g;
+ cr = b;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in Av1ColorConversionParameters parameters)
+ {
+ Vector256 r = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.RedChromaScale), cr, y);
+ Vector256 g = Vector256.MultiplyAddEstimate(
+ Vector256.Create(-parameters.GreenBlueChromaScale),
+ cb,
+ Vector256.MultiplyAddEstimate(Vector256.Create(-parameters.GreenRedChromaScale), cr, y));
+ Vector256 b = Vector256.MultiplyAddEstimate(Vector256.Create(parameters.BlueChromaScale), cb, y);
+
+ y = r;
+ cb = g;
+ cr = b;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in Av1ColorConversionParameters parameters)
+ {
+ Vector512 r = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.RedChromaScale), cr, y);
+ Vector512 g = Vector512.MultiplyAddEstimate(
+ Vector512.Create(-parameters.GreenBlueChromaScale),
+ cb,
+ Vector512.MultiplyAddEstimate(Vector512.Create(-parameters.GreenRedChromaScale), cr, y));
+ Vector512 b = Vector512.MultiplyAddEstimate(Vector512.Create(parameters.BlueChromaScale), cb, y);
+
+ y = r;
+ cb = g;
+ cr = b;
+ }
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector128 r,
+ Vector128 g,
+ Vector128 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector128 y,
+ out Vector128 cb,
+ out Vector128 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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector256 r,
+ Vector256 g,
+ Vector256 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector256 y,
+ out Vector256 cb,
+ out Vector256 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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector512 r,
+ Vector512 g,
+ Vector512 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector512 y,
+ out Vector512 cb,
+ out Vector512 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);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs
new file mode 100644
index 000000000..990113ead
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ConstantLuminanceOperator.cs
@@ -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
+{
+ ///
+ /// Implements H.273 constant-luminance conversion for scalar and SIMD lanes.
+ ///
+ internal readonly struct Av1ConstantLuminanceColorOperator : IAv1ColorOperator
+ {
+ ///
+ public static bool ChromaUsesLumaRange => false;
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector128 y, ref Vector128 cb, ref Vector128 cr, in Av1ColorConversionParameters parameters)
+ {
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector128 blueScale = Vector128.ConditionalSelect(
+ Vector128.LessThanOrEqual(cb, Vector128.Zero),
+ Vector128.Create(scales.NegativeBlue),
+ Vector128.Create(scales.PositiveBlue));
+ Vector128 redScale = Vector128.ConditionalSelect(
+ Vector128.LessThanOrEqual(cr, Vector128.Zero),
+ Vector128.Create(scales.NegativeRed),
+ Vector128.Create(scales.PositiveRed));
+ Vector128 nonlinearBlue = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * blueScale, cb, y);
+ Vector128 nonlinearRed = Vector128.MultiplyAddEstimate(Vector128.Create(2F) * redScale, cr, y);
+ Vector128 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
+ Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
+ Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed);
+ Vector128 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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector256 y, ref Vector256 cb, ref Vector256 cr, in Av1ColorConversionParameters parameters)
+ {
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector256 blueScale = Vector256.ConditionalSelect(
+ Vector256.LessThanOrEqual(cb, Vector256.Zero),
+ Vector256.Create(scales.NegativeBlue),
+ Vector256.Create(scales.PositiveBlue));
+ Vector256 redScale = Vector256.ConditionalSelect(
+ Vector256.LessThanOrEqual(cr, Vector256.Zero),
+ Vector256.Create(scales.NegativeRed),
+ Vector256.Create(scales.PositiveRed));
+ Vector256 nonlinearBlue = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * blueScale, cb, y);
+ Vector256 nonlinearRed = Vector256.MultiplyAddEstimate(Vector256.Create(2F) * redScale, cr, y);
+ Vector256 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
+ Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
+ Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed);
+ Vector256 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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(ref Vector512 y, ref Vector512 cb, ref Vector512 cr, in Av1ColorConversionParameters parameters)
+ {
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector512 blueScale = Vector512.ConditionalSelect(
+ Vector512.LessThanOrEqual(cb, Vector512.Zero),
+ Vector512.Create(scales.NegativeBlue),
+ Vector512.Create(scales.PositiveBlue));
+ Vector512 redScale = Vector512.ConditionalSelect(
+ Vector512.LessThanOrEqual(cr, Vector512.Zero),
+ Vector512.Create(scales.NegativeRed),
+ Vector512.Create(scales.PositiveRed));
+ Vector512 nonlinearBlue = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * blueScale, cb, y);
+ Vector512 nonlinearRed = Vector512.MultiplyAddEstimate(Vector512.Create(2F) * redScale, cr, y);
+ Vector512 linearY = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, y);
+ Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearBlue);
+ Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearRed);
+ Vector512 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;
+ }
+
+ ///
+ [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));
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector128 r,
+ Vector128 g,
+ Vector128 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector128 y,
+ out Vector128 cb,
+ out Vector128 cr)
+ {
+ Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
+ Vector128 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
+ Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b);
+ Vector128 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 blueDifference = b - y;
+ Vector128 redDifference = r - y;
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector128 blueScale = Vector128.ConditionalSelect(
+ Vector128.LessThanOrEqual(blueDifference, Vector128.Zero),
+ Vector128.Create(scales.NegativeBlue),
+ Vector128.Create(scales.PositiveBlue));
+ Vector128 redScale = Vector128.ConditionalSelect(
+ Vector128.LessThanOrEqual(redDifference, Vector128.Zero),
+ Vector128.Create(scales.NegativeRed),
+ Vector128.Create(scales.PositiveRed));
+
+ cb = blueDifference / (Vector128.Create(2F) * blueScale);
+ cr = redDifference / (Vector128.Create(2F) * redScale);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector256 r,
+ Vector256 g,
+ Vector256 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector256 y,
+ out Vector256 cb,
+ out Vector256 cr)
+ {
+ Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
+ Vector256 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
+ Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b);
+ Vector256 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 blueDifference = b - y;
+ Vector256 redDifference = r - y;
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector256 blueScale = Vector256.ConditionalSelect(
+ Vector256.LessThanOrEqual(blueDifference, Vector256.Zero),
+ Vector256.Create(scales.NegativeBlue),
+ Vector256.Create(scales.PositiveBlue));
+ Vector256 redScale = Vector256.ConditionalSelect(
+ Vector256.LessThanOrEqual(redDifference, Vector256.Zero),
+ Vector256.Create(scales.NegativeRed),
+ Vector256.Create(scales.PositiveRed));
+
+ cb = blueDifference / (Vector256.Create(2F) * blueScale);
+ cr = redDifference / (Vector256.Create(2F) * redScale);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector512 r,
+ Vector512 g,
+ Vector512 b,
+ in Av1ColorConversionParameters parameters,
+ out Vector512 y,
+ out Vector512 cb,
+ out Vector512 cr)
+ {
+ Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, r);
+ Vector512 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, g);
+ Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, b);
+ Vector512 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 blueDifference = b - y;
+ Vector512 redDifference = r - y;
+ Av1ConstantLuminanceScales scales = parameters.ConstantLuminanceScales;
+ Vector512 blueScale = Vector512.ConditionalSelect(
+ Vector512.LessThanOrEqual(blueDifference, Vector512.Zero),
+ Vector512.Create(scales.NegativeBlue),
+ Vector512.Create(scales.PositiveBlue));
+ Vector512 redScale = Vector512.ConditionalSelect(
+ Vector512.LessThanOrEqual(redDifference, Vector512.Zero),
+ Vector512.Create(scales.NegativeRed),
+ Vector512.Create(scales.PositiveRed));
+
+ cb = blueDifference / (Vector512.Create(2F) * blueScale);
+ cr = redDifference / (Vector512.Create(2F) * redScale);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs
new file mode 100644
index 000000000..146623057
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.ICtCpOperator.cs
@@ -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
+{
+ ///
+ /// Implements BT.2100 ICtCp conversion for scalar and SIMD lanes.
+ ///
+ internal readonly struct Av1ICtCpColorOperator : IAv1ColorOperator
+ {
+ ///
+ /// The PQ Ct contribution to nonlinear L.
+ ///
+ public const float PqCtToL = 0.008609037037932756F;
+
+ ///
+ /// The PQ Cp contribution to nonlinear L.
+ ///
+ public const float PqCpToL = 0.11102962500302596F;
+
+ ///
+ /// The PQ Ct contribution to nonlinear S.
+ ///
+ public const float PqCtToS = 0.5600313357106791F;
+
+ ///
+ /// The PQ Cp contribution to nonlinear S.
+ ///
+ public const float PqCpToS = -0.32062717498731885F;
+
+ ///
+ /// The HLG Ct contribution to nonlinear L.
+ ///
+ public const float HlgCtToL = 0.015718580108730413F;
+
+ ///
+ /// The HLG Cp contribution to nonlinear L.
+ ///
+ public const float HlgCpToL = 0.2095810681164055F;
+
+ ///
+ /// The HLG Ct contribution to nonlinear S.
+ ///
+ public const float HlgCtToS = 1.0212710798422342F;
+
+ ///
+ /// The HLG Cp contribution to nonlinear S.
+ ///
+ public const float HlgCpToS = -0.6052744909924315F;
+
+ ///
+ /// The linear L contribution to red.
+ ///
+ public const float LToRed = 3.4366066943330784F;
+
+ ///
+ /// The linear M contribution to red.
+ ///
+ public const float MToRed = -2.50645211865627F;
+
+ ///
+ /// The linear S contribution to red.
+ ///
+ public const float SToRed = 0.06984542432319148F;
+
+ ///
+ /// The linear L contribution to green.
+ ///
+ public const float LToGreen = -0.7913295555989287F;
+
+ ///
+ /// The linear M contribution to green.
+ ///
+ public const float MToGreen = 1.9836004517922907F;
+
+ ///
+ /// The linear S contribution to green.
+ ///
+ public const float SToGreen = -0.192270896193362F;
+
+ ///
+ /// The linear L contribution to blue.
+ ///
+ public const float LToBlue = -0.025949899690592672F;
+
+ ///
+ /// The linear M contribution to blue.
+ ///
+ public const float MToBlue = -0.09891371471172644F;
+
+ ///
+ /// The linear S contribution to blue.
+ ///
+ public const float SToBlue = 1.1248636144023192F;
+
+ ///
+ /// The linear red contribution to L.
+ ///
+ public const float RedToL = 1688F / 4096F;
+
+ ///
+ /// The linear green contribution to L.
+ ///
+ public const float GreenToL = 2146F / 4096F;
+
+ ///
+ /// The linear blue contribution to L.
+ ///
+ public const float BlueToL = 262F / 4096F;
+
+ ///
+ /// The linear red contribution to M.
+ ///
+ public const float RedToM = 683F / 4096F;
+
+ ///
+ /// The linear green contribution to M.
+ ///
+ public const float GreenToM = 2951F / 4096F;
+
+ ///
+ /// The linear blue contribution to M.
+ ///
+ public const float BlueToM = 462F / 4096F;
+
+ ///
+ /// The linear red contribution to S.
+ ///
+ public const float RedToS = 99F / 4096F;
+
+ ///
+ /// The linear green contribution to S.
+ ///
+ public const float GreenToS = 309F / 4096F;
+
+ ///
+ /// The linear blue contribution to S.
+ ///
+ public const float BlueToS = 3688F / 4096F;
+
+ ///
+ /// The PQ nonlinear L contribution to Ct.
+ ///
+ public const float PqLToCt = 6610F / 4096F;
+
+ ///
+ /// The PQ nonlinear M contribution to Ct.
+ ///
+ public const float PqMToCt = -13613F / 4096F;
+
+ ///
+ /// The PQ nonlinear S contribution to Ct.
+ ///
+ public const float PqSToCt = 7003F / 4096F;
+
+ ///
+ /// The PQ nonlinear L contribution to Cp.
+ ///
+ public const float PqLToCp = 17933F / 4096F;
+
+ ///
+ /// The PQ nonlinear M contribution to Cp.
+ ///
+ public const float PqMToCp = -17390F / 4096F;
+
+ ///
+ /// The PQ nonlinear S contribution to Cp.
+ ///
+ public const float PqSToCp = -543F / 4096F;
+
+ ///
+ /// The HLG nonlinear L contribution to Ct.
+ ///
+ public const float HlgLToCt = 3625F / 4096F;
+
+ ///
+ /// The HLG nonlinear M contribution to Ct.
+ ///
+ public const float HlgMToCt = -7465F / 4096F;
+
+ ///
+ /// The HLG nonlinear S contribution to Ct.
+ ///
+ public const float HlgSToCt = 3840F / 4096F;
+
+ ///
+ /// The HLG nonlinear L contribution to Cp.
+ ///
+ public const float HlgLToCp = 9500F / 4096F;
+
+ ///
+ /// The HLG nonlinear M contribution to Cp.
+ ///
+ public const float HlgMToCp = -9212F / 4096F;
+
+ ///
+ /// The HLG nonlinear S contribution to Cp.
+ ///
+ public const float HlgSToCp = -288F / 4096F;
+
+ ///
+ public static bool ChromaUsesLumaRange => false;
+
+ ///
+ [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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector128 intensity,
+ ref Vector128 ct,
+ ref Vector128 cp,
+ in Av1ColorConversionParameters parameters)
+ {
+ bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
+ Vector128 ctContribution = Vector128.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
+ Vector128 cpContribution = Vector128.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
+ Vector128 nonlinearL = intensity + ctContribution + cpContribution;
+ Vector128 nonlinearM = intensity - ctContribution - cpContribution;
+ Vector128 nonlinearS = Vector128.MultiplyAddEstimate(
+ Vector128.Create(isHlg ? HlgCpToS : PqCpToS),
+ cp,
+ Vector128.MultiplyAddEstimate(Vector128.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity));
+ Vector128 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
+ Vector128 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
+ Vector128 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
+ Vector128 linearRed = Vector128.MultiplyAddEstimate(
+ Vector128.Create(SToRed),
+ linearS,
+ Vector128.MultiplyAddEstimate(Vector128.Create(MToRed), linearM, Vector128.Create(LToRed) * linearL));
+ Vector128 linearGreen = Vector128.MultiplyAddEstimate(
+ Vector128.Create(SToGreen),
+ linearS,
+ Vector128.MultiplyAddEstimate(Vector128.Create(MToGreen), linearM, Vector128.Create(LToGreen) * linearL));
+ Vector128 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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector256 intensity,
+ ref Vector256 ct,
+ ref Vector256 cp,
+ in Av1ColorConversionParameters parameters)
+ {
+ bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
+ Vector256 ctContribution = Vector256.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
+ Vector256 cpContribution = Vector256.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
+ Vector256 nonlinearL = intensity + ctContribution + cpContribution;
+ Vector256 nonlinearM = intensity - ctContribution - cpContribution;
+ Vector256 nonlinearS = Vector256.MultiplyAddEstimate(
+ Vector256.Create(isHlg ? HlgCpToS : PqCpToS),
+ cp,
+ Vector256.MultiplyAddEstimate(Vector256.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity));
+ Vector256 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
+ Vector256 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
+ Vector256 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
+ Vector256 linearRed = Vector256.MultiplyAddEstimate(
+ Vector256.Create(SToRed),
+ linearS,
+ Vector256.MultiplyAddEstimate(Vector256.Create(MToRed), linearM, Vector256.Create(LToRed) * linearL));
+ Vector256 linearGreen = Vector256.MultiplyAddEstimate(
+ Vector256.Create(SToGreen),
+ linearS,
+ Vector256.MultiplyAddEstimate(Vector256.Create(MToGreen), linearM, Vector256.Create(LToGreen) * linearL));
+ Vector256 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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector512 intensity,
+ ref Vector512 ct,
+ ref Vector512 cp,
+ in Av1ColorConversionParameters parameters)
+ {
+ bool isHlg = parameters.TransferCharacteristics == ObuTransferCharacteristics.Hlg;
+ Vector512 ctContribution = Vector512.Create(isHlg ? HlgCtToL : PqCtToL) * ct;
+ Vector512 cpContribution = Vector512.Create(isHlg ? HlgCpToL : PqCpToL) * cp;
+ Vector512 nonlinearL = intensity + ctContribution + cpContribution;
+ Vector512 nonlinearM = intensity - ctContribution - cpContribution;
+ Vector512 nonlinearS = Vector512.MultiplyAddEstimate(
+ Vector512.Create(isHlg ? HlgCpToS : PqCpToS),
+ cp,
+ Vector512.MultiplyAddEstimate(Vector512.Create(isHlg ? HlgCtToS : PqCtToS), ct, intensity));
+ Vector512 linearL = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearL);
+ Vector512 linearM = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearM);
+ Vector512 linearS = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, nonlinearS);
+ Vector512 linearRed = Vector512.MultiplyAddEstimate(
+ Vector512.Create(SToRed),
+ linearS,
+ Vector512.MultiplyAddEstimate(Vector512.Create(MToRed), linearM, Vector512.Create(LToRed) * linearL));
+ Vector512 linearGreen = Vector512.MultiplyAddEstimate(
+ Vector512.Create(SToGreen),
+ linearS,
+ Vector512.MultiplyAddEstimate(Vector512.Create(MToGreen), linearM, Vector512.Create(LToGreen) * linearL));
+ Vector512 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);
+ }
+
+ ///
+ [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);
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector128 red,
+ Vector128 green,
+ Vector128 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector128 intensity,
+ out Vector128 ct,
+ out Vector128 cp)
+ {
+ Vector128 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
+ Vector128 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
+ Vector128 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
+ Vector128 linearL = Vector128.MultiplyAddEstimate(
+ Vector128.Create(RedToL),
+ linearRed,
+ Vector128.MultiplyAddEstimate(Vector128.Create(GreenToL), linearGreen, Vector128.Create(BlueToL) * linearBlue));
+ Vector128 linearM = Vector128.MultiplyAddEstimate(
+ Vector128.Create(RedToM),
+ linearRed,
+ Vector128.MultiplyAddEstimate(Vector128.Create(GreenToM), linearGreen, Vector128.Create(BlueToM) * linearBlue));
+ Vector128 linearS = Vector128.MultiplyAddEstimate(
+ Vector128.Create(RedToS),
+ linearRed,
+ Vector128.MultiplyAddEstimate(Vector128.Create(GreenToS), linearGreen, Vector128.Create(BlueToS) * linearBlue));
+ Vector128 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
+ Vector128 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
+ Vector128 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));
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector256 red,
+ Vector256 green,
+ Vector256 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector256 intensity,
+ out Vector256 ct,
+ out Vector256 cp)
+ {
+ Vector256 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
+ Vector256 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
+ Vector256 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
+ Vector256 linearL = Vector256.MultiplyAddEstimate(
+ Vector256.Create(RedToL),
+ linearRed,
+ Vector256.MultiplyAddEstimate(Vector256.Create(GreenToL), linearGreen, Vector256.Create(BlueToL) * linearBlue));
+ Vector256 linearM = Vector256.MultiplyAddEstimate(
+ Vector256.Create(RedToM),
+ linearRed,
+ Vector256.MultiplyAddEstimate(Vector256.Create(GreenToM), linearGreen, Vector256.Create(BlueToM) * linearBlue));
+ Vector256 linearS = Vector256.MultiplyAddEstimate(
+ Vector256.Create(RedToS),
+ linearRed,
+ Vector256.MultiplyAddEstimate(Vector256.Create(GreenToS), linearGreen, Vector256.Create(BlueToS) * linearBlue));
+ Vector256 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
+ Vector256 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
+ Vector256 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));
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector512 red,
+ Vector512 green,
+ Vector512 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector512 intensity,
+ out Vector512 ct,
+ out Vector512 cp)
+ {
+ Vector512 linearRed = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, red);
+ Vector512 linearGreen = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, green);
+ Vector512 linearBlue = Av1TransferFunctions.ToLinear(parameters.TransferCharacteristics, blue);
+ Vector512 linearL = Vector512.MultiplyAddEstimate(
+ Vector512.Create(RedToL),
+ linearRed,
+ Vector512.MultiplyAddEstimate(Vector512.Create(GreenToL), linearGreen, Vector512.Create(BlueToL) * linearBlue));
+ Vector512 linearM = Vector512.MultiplyAddEstimate(
+ Vector512.Create(RedToM),
+ linearRed,
+ Vector512.MultiplyAddEstimate(Vector512.Create(GreenToM), linearGreen, Vector512.Create(BlueToM) * linearBlue));
+ Vector512 linearS = Vector512.MultiplyAddEstimate(
+ Vector512.Create(RedToS),
+ linearRed,
+ Vector512.MultiplyAddEstimate(Vector512.Create(GreenToS), linearGreen, Vector512.Create(BlueToS) * linearBlue));
+ Vector512 nonlinearL = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearL);
+ Vector512 nonlinearM = Av1TransferFunctions.ToGamma(parameters.TransferCharacteristics, linearM);
+ Vector512 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));
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs
new file mode 100644
index 000000000..f3bf16cc0
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.IdentityOperator.cs
@@ -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
+{
+ ///
+ /// Implements direct G, B, and R plane mapping for scalar and SIMD lanes.
+ ///
+ internal readonly struct Av1IdentityColorOperator : IAv1ColorOperator
+ {
+ ///
+ public static bool ChromaUsesLumaRange => true;
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector128 green,
+ ref Vector128 blue,
+ ref Vector128 red,
+ in Av1ColorConversionParameters parameters)
+ {
+ Vector128 g = green;
+ green = red;
+ red = blue;
+ blue = g;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector256 green,
+ ref Vector256 blue,
+ ref Vector256 red,
+ in Av1ColorConversionParameters parameters)
+ {
+ Vector256 g = green;
+ green = red;
+ red = blue;
+ blue = g;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertToRgb(
+ ref Vector512 green,
+ ref Vector512 blue,
+ ref Vector512 red,
+ in Av1ColorConversionParameters parameters)
+ {
+ Vector512 g = green;
+ green = red;
+ red = blue;
+ blue = g;
+ }
+
+ ///
+ [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;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector128 red,
+ Vector128 green,
+ Vector128 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector128 component0,
+ out Vector128 component1,
+ out Vector128 component2)
+ {
+ component0 = green;
+ component1 = blue;
+ component2 = red;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector256 red,
+ Vector256 green,
+ Vector256 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector256 component0,
+ out Vector256 component1,
+ out Vector256 component2)
+ {
+ component0 = green;
+ component1 = blue;
+ component2 = red;
+ }
+
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ConvertFromRgb(
+ Vector512 red,
+ Vector512 green,
+ Vector512 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector512 component0,
+ out Vector512 component1,
+ out Vector512 component2)
+ {
+ component0 = green;
+ component1 = blue;
+ component2 = red;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs
new file mode 100644
index 000000000..5b29b067d
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorConverter.Operator.cs
@@ -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;
+
+///
+/// Provides the static operator contract and SIMD traversal used by AV1 color converters.
+///
+internal abstract partial class Av1ColorConverterBase
+{
+ ///
+ /// Defines color-model arithmetic for scalar and SIMD lanes in both conversion directions.
+ ///
+ internal interface IAv1ColorOperator
+ {
+ ///
+ /// Gets a value indicating whether chroma uses the luma range rather than the centered chroma range.
+ ///
+ public static abstract bool ChromaUsesLumaRange { get; }
+
+ ///
+ /// Converts one normalized AV1 sample to RGB.
+ ///
+ /// The first encoded component, replaced by red.
+ /// The second encoded component, replaced by green.
+ /// The third encoded component, replaced by blue.
+ /// The resolved H.273 conversion parameters.
+ public static abstract void ConvertToRgb(
+ ref float component0,
+ ref float component1,
+ ref float component2,
+ in Av1ColorConversionParameters parameters);
+
+ ///
+ /// Converts four normalized AV1 samples to RGB.
+ ///
+ /// The first encoded component lanes, replaced by red.
+ /// The second encoded component lanes, replaced by green.
+ /// The third encoded component lanes, replaced by blue.
+ /// The resolved H.273 conversion parameters.
+ public static abstract void ConvertToRgb(
+ ref Vector128 component0,
+ ref Vector128 component1,
+ ref Vector128 component2,
+ in Av1ColorConversionParameters parameters);
+
+ ///
+ /// Converts eight normalized AV1 samples to RGB.
+ ///
+ /// The first encoded component lanes, replaced by red.
+ /// The second encoded component lanes, replaced by green.
+ /// The third encoded component lanes, replaced by blue.
+ /// The resolved H.273 conversion parameters.
+ public static abstract void ConvertToRgb(
+ ref Vector256 component0,
+ ref Vector256 component1,
+ ref Vector256 component2,
+ in Av1ColorConversionParameters parameters);
+
+ ///
+ /// Converts sixteen normalized AV1 samples to RGB.
+ ///
+ /// The first encoded component lanes, replaced by red.
+ /// The second encoded component lanes, replaced by green.
+ /// The third encoded component lanes, replaced by blue.
+ /// The resolved H.273 conversion parameters.
+ public static abstract void ConvertToRgb(
+ ref Vector512 component0,
+ ref Vector512 component1,
+ ref Vector512 component2,
+ in Av1ColorConversionParameters parameters);
+
+ ///
+ /// Converts one normalized RGB sample to AV1 components.
+ ///
+ /// The normalized red component.
+ /// The normalized green component.
+ /// The normalized blue component.
+ /// The resolved H.273 conversion parameters.
+ /// The first converted component.
+ /// The second converted component.
+ /// The third converted component.
+ public static abstract void ConvertFromRgb(
+ float red,
+ float green,
+ float blue,
+ in Av1ColorConversionParameters parameters,
+ out float component0,
+ out float component1,
+ out float component2);
+
+ ///
+ /// Converts four normalized RGB samples to AV1 components.
+ ///
+ /// The normalized red lanes.
+ /// The normalized green lanes.
+ /// The normalized blue lanes.
+ /// The resolved H.273 conversion parameters.
+ /// The first converted component lanes.
+ /// The second converted component lanes.
+ /// The third converted component lanes.
+ public static abstract void ConvertFromRgb(
+ Vector128 red,
+ Vector128 green,
+ Vector128 blue,
+ in Av1ColorConversionParameters parameters,
+ out Vector128 component0,
+ out Vector128 component1,
+ out Vector128